Revert "CompactSeedQR format"
@@ -2,17 +2,6 @@
|
||||
|
||||

|
||||
|
||||
---------------
|
||||
|
||||
* [Project Summary](#project-summary)
|
||||
* [Shopping List](#shopping-list)
|
||||
* [Software Installation](#software-installation)
|
||||
* [Verifying Your Software](#verifying-your-software)
|
||||
* [Enclosure Designs](#enclosure-designs)
|
||||
* [SeedQR Printable Templates](#seedqr-printable-templates)
|
||||
* [Manual Installation Instructions](#manual-installation-instructions)
|
||||
|
||||
|
||||
---------------
|
||||
|
||||
# Project Summary
|
||||
@@ -44,7 +33,7 @@ If you have specific questions about the project, our [Telegram Group](https://t
|
||||
* Responsive, event-driven user interface
|
||||
|
||||
### Considerations:
|
||||
* Built for compatibility with Specter Desktop, Sparrow, and BlueWallet Vaults
|
||||
* Built for compatibility with Specter-desktop, Sparrow and BlueWallet Vaults
|
||||
* Device takes up to 60 seconds to boot before menu appears (be patient!)
|
||||
* Always test your setup before transfering larger amounts of bitcoin (try testnet first!)
|
||||
* Taproot not quite yet supported
|
||||
@@ -78,12 +67,14 @@ Notes:
|
||||
|
||||
---------------
|
||||
|
||||
# Software Installation
|
||||
The quickest and easiest way to install the software is to download the most recent "seedsigner_X_X_X.zip" file in the [software releases](https://github.com/SeedSigner/seedsigner/releases) section of this repository.
|
||||
# Important Note on Software Installation
|
||||
|
||||
After downloading the .zip file, extract the seedsigner .img file, and write it to a MicroSD card (at least 4GB in size or larger). Then install the MicroSD in the assembled hardware and off you go. If your goal is a more trustless installation, you can follow the [manual installation instructions](docs/manual_installation.md).
|
||||
The quickest and easiest way to install the software is to download the most recent "seedsigner_X_X_X.zip" file in the [software releases](https://github.com/SeedSigner/seedsigner/releases) section of this repository. After downloading the .zip file, extract the seedsigner .img file, and write it to a MicroSD card (at least 4GB in size or larger). Then install the MicroSD in the assembled hardware and off you go. If your goal is a more trustless installation, you can follow the manual software installation instructions below.
|
||||
|
||||
---------------
|
||||
|
||||
# Important Note on Software Authenticity Verification
|
||||
|
||||
## Verifying Your Software
|
||||
You can verify the data integrity and authenticity of the latest release with as little as three commands (though moving forward you will have to replace the version in the following commands with the version number you are attempting to validate). This process assumes that you have navigated to a folder where you have these four relevant files present:
|
||||
|
||||
* seedsigner_pubkey.gpg (from the main folder of this repo)
|
||||
@@ -143,27 +134,181 @@ The upper and lower portions of the enclosure can be printed using a standard FD
|
||||
|
||||
---------------
|
||||
|
||||
# SeedQR Printable Templates
|
||||
You can use SeedSigner to export your seed to a hand-transcribed SeedQR format that enables you to instantly load your seed back into SeedSigner.
|
||||
# Manual Software Installation Instructions:
|
||||
|
||||
[More information about SeedQRs](docs/seed_qr/README.md)
|
||||
Begin by acquiring a specific copy of the Raspberry Pi Lite operating system, dated 2021-05-28; this version can be found here:
|
||||
|
||||
<table align="center">
|
||||
<tr><td><img src="docs/seed_qr/img/handmade_qr.jpg"></td></tr>
|
||||
</table>
|
||||
https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/
|
||||
|
||||
Standard SeedQR templates:
|
||||
* [12-word SeedQR template (25x25)](docs/seed_qr/printable_templates/12words_seedqr_template.pdf)
|
||||
* [24-word SeedQR template (29x29)](docs/seed_qr/printable_templates/24words_seedqr_template.pdf)
|
||||
* [Baseball card template: 24-word SeedQR (29x29)](docs/seed_qr/printable_templates/Seed_QR_Card.pdf)
|
||||
Best practice is to verify the downloaded .zip file containing the Raspberry Pi Lite OS matches the published SHA256 hash of the file; for additional reference that hash is: c5dad159a2775c687e9281b1a0e586f7471690ae28f2f2282c90e7d59f64273c. After verifying the file's data integrity, you can decompress the .zip file to obtain the operating system image that it contains. You can then use Balena's Etcher tool (https://www.balena.io/etcher/) to write the Raspberry Pi Lite software image to a memory card (4 GB or larger). It's important to note that an image authoring tool must be used (the operating system image cannot be simply copied into a file storage partition on the memory card).
|
||||
|
||||
CompactSeedQR templates:
|
||||
* [12-word CompactSeedQR template (21x21)](docs/seed_qr/printable_templates/compact_seedqr/12words_compactseedqr_template.pdf)
|
||||
* [24-word CompactSeedQR template (25x25)](docs/seed_qr/printable_templates/compact_seedqr/24words_compactseedqr_template.pdf)
|
||||
The manual SeedSigner installation and configuration process requires an internet connection on the device to download the necessary libraries and code. But because the Pi Zero 1.3 does not have onboard wifi, you have two options:
|
||||
|
||||
_note: CompactSeedQR is an advanced feature that can be enabled in Settings_
|
||||
1. Run these steps on a separate Raspberry Pi 2/3/4 or Zero W which can connect to the internet and then transfer the SD card to the Pi Zero 1.3 when complete.
|
||||
2. OR configure the Pi Zero 1.3 directly by relaying through your computer's internet connection over USB. See instructions [here](docs/usb_relay.md).
|
||||
|
||||
---------------
|
||||
For the following steps you'll need to either connect a keyboard & monitor to the network-connected Raspberry Pi you are working with, or SSH into the Pi if you're familiar with that process.
|
||||
|
||||
# Manual Installation Instructions
|
||||
see the docs: [Manual Installation Instructions](docs/manual_installation.md)
|
||||
### Configure the Pi
|
||||
|
||||
First things first, verify that you are using the correct version of the Raspberry Pi Lite operating system by typing the command:
|
||||
```
|
||||
cat /etc/os-release
|
||||
```
|
||||
The output of this command should match the following text:
|
||||
```
|
||||
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
|
||||
NAME="Raspbian GNU/Linux"
|
||||
VERSION_ID="10"
|
||||
VERSION="10 (buster)"
|
||||
VERSION_CODENAME=buster
|
||||
ID=raspbian
|
||||
ID_LIKE=debian
|
||||
HOME_URL="http://www.raspbian.org/"
|
||||
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
|
||||
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
|
||||
```
|
||||
|
||||
Now launch the Raspberry Pi's System Configuration tool using the command:
|
||||
```
|
||||
sudo raspi-config
|
||||
```
|
||||
|
||||
Set the following:
|
||||
* `Interface Options`:
|
||||
* `Camera`: enable
|
||||
* `SPI`: enable
|
||||
* `Localisation Options`:
|
||||
* `Locale`: arrow up and down through the list and select or deselect languages with the spacebar.
|
||||
* Deselect the default language option that is selected
|
||||
* Select `en_US.UTF-8 UTF-8` for US English
|
||||
* You will also need to configure the WiFi settings if you are using the #1 option above to connect to the internet
|
||||
|
||||
When you exit the System Configuration tool, you will be prompted to reboot the system; allow the system to reboot and continue with these instructions.
|
||||
|
||||
Install these dependencies (you can use this entire text string to install them all at once):
|
||||
```
|
||||
sudo apt-get update && sudo apt-get install -y wiringpi python3-pip python3-numpy python-pil libopenjp2-7 git python3-opencv libzbar0 python3-picamera libatlas-base-dev qrencode
|
||||
```
|
||||
|
||||
Install the [C library for Broadcom BCM 2835](http://www.airspayce.com/mikem/bcm2835/):
|
||||
```
|
||||
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.60.tar.gz
|
||||
tar zxvf bcm2835-1.60.tar.gz
|
||||
cd bcm2835-1.60/
|
||||
sudo ./configure
|
||||
sudo make && sudo make check && sudo make install
|
||||
cd ..
|
||||
rm bcm2835-1.60.tar.gz
|
||||
```
|
||||
|
||||
Install the "virtualenvwrapper" tool:
|
||||
```
|
||||
pip3 install virtualenvwrapper
|
||||
```
|
||||
|
||||
Edit your bash profile with the command `nano ~/.profile` and add the following to the end:
|
||||
```
|
||||
export WORKON_HOME=$HOME/.envs
|
||||
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
|
||||
source /home/pi/.local/bin/virtualenvwrapper.sh
|
||||
```
|
||||
Then `CTRL-X` and `y` to exit and save changes.
|
||||
|
||||
Now create the python virtualenv for SeedSigner with these two commands:
|
||||
```
|
||||
source ~/.profile
|
||||
mkvirtualenv --python=python3 seedsigner-env
|
||||
```
|
||||
|
||||
Now download the SeedSigner code:
|
||||
```
|
||||
git clone https://github.com/SeedSigner/seedsigner
|
||||
```
|
||||
|
||||
Install the necessary Python dependencies:
|
||||
```
|
||||
cd seedsigner
|
||||
pip3 install -r requirements.txt
|
||||
cd ..
|
||||
```
|
||||
|
||||
Modify the systemd to run SeedSigner at boot:
|
||||
```
|
||||
sudo nano /etc/systemd/system/seedsigner.service
|
||||
```
|
||||
|
||||
Add the following contents to the text file that was created:
|
||||
```
|
||||
[Unit]
|
||||
Description=Seedsigner
|
||||
|
||||
[Service]
|
||||
User=pi
|
||||
WorkingDirectory=/home/pi/seedsigner/src/
|
||||
ExecStart=/home/pi/.envs/seedsigner-env/bin/python main.py
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Use `CTRL-X` and `y` to exit and save changes.
|
||||
|
||||
Run `sudo systemctl enable seedsigner.service` to enable service on boot. (This will restart the seedsigner code automatically at startup and if it crashes.)
|
||||
|
||||
Now reboot the Raspberry Pi with the command:
|
||||
```
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
After the Raspberry Pi reboots, you should see the SeedSigner splash screen and the SeedSigner menu subsequently appear on the LCD screen (note that it can take up to 60 seconds for the menu to appear).
|
||||
|
||||
Next, disable and remove the system's virtual memory / swap file with the commands:
|
||||
```
|
||||
sudo apt remove dphys-swapfile
|
||||
sudo rm /var/swap
|
||||
```
|
||||
|
||||
For those who will use the SeedSigner installation for testing/development, it can be helpful to change the system's host name so it doesn't potentially conflict with other Raspberry Pi's that may already be present on your network. (For those who don't plan to use the installation for testing or development, you can skip this portion of the process.) To change the host name first edit the "hostname" with the command:
|
||||
```
|
||||
sudo nano /etc/hostname
|
||||
```
|
||||
and change "raspberrypi" to "seedsigner" (or another name). Use `CTRL-X` and `y` to exit and save changes. You'll also need to edit the "hosts" file with the command:
|
||||
```
|
||||
sudo nano /etc/hosts
|
||||
```
|
||||
and change "raspberrypi" to "seedsigner" (or the other name you previously chose). Use `CTRL-X` and `y` to exit and save changes.
|
||||
|
||||
It's also a good idea to change the system's password from the default "raspberry". Do do this, use the command:
|
||||
```
|
||||
passwd
|
||||
```
|
||||
You will be prompted to enter the current password (raspberry) and then to enter a new password twice. In our prepared release image, the password used is "AirG@pped!" (without quotes).
|
||||
|
||||
If you plan to use your installation on a Raspberry Pi that is not a Zero version 1.3, but rather on a Raspberry Pi that has WiFi and Bluetooth capabilities, it is a good idea to disable the following WiFi & Bluetooth, as well as other relevant services (assuming you are not creating this installation for testing/development purposes). Enter the followiing commands to disable WiFi, Bluetooth, & other relevant services:
|
||||
```
|
||||
sudo systemctl disable bluetooth.service
|
||||
sudo systemctl disable wpa_supplicant.service
|
||||
sudo systemctl disable dhcpcd.service
|
||||
sudo systemctl disable sshd.service
|
||||
sudo systemctl disable networking.service
|
||||
sudo systemctl disable dphys-swapfile.service
|
||||
sudo ifconfig wlan0 down
|
||||
```
|
||||
Please note that if you are using WiFi to connect/interact with your Raspberry Pi, the last command will sever that connection.
|
||||
|
||||
You can now safely power the Raspberry Pi off from the SeedSigner main menu.
|
||||
|
||||
If you do not plan to use your installation for testing/development, it is also a good idea to disable WiFi and Bluetooth by editing the config.txt file found in the installation's "boot" partition. You can add the following text to the end of that file with any simple text editor (Windows: Notepad, Mac: TextEdit, Linux: nano):
|
||||
```
|
||||
dtoverlay=disable-bt
|
||||
dtoverlay=pi3-disable-wifi
|
||||
```
|
||||
|
||||
If you used option #2 above and don't plan to continue to access your SeedSigner via SSH over USB, it is a good idea to reverse the steps you took to enable it -- those instructions can be found near the end of [this guide](docs/usb_relay.md).
|
||||
|
||||
Please remember that it can take up to a minute for the GUI to appear when subsequently powering your SeedSigner on.
|
||||
|
||||
### Optional: Run the tests
|
||||
see: [tests/README.md](tests/README.md)
|
||||
|
||||
@@ -27,36 +27,3 @@ For the seed in the snippet, you should see:
|
||||
Take the output and paste it into a [QR code generator](https://www.the-qrcode-generator.com/).
|
||||
|
||||
Start up SeedSigner's UI to import a seed from a QR code. Scan the new QR code and you're good to go!
|
||||
|
||||
|
||||
# Advanced developer notes
|
||||
|
||||
## Backup an SD card
|
||||
|
||||
You can back up and restore any size SD card but the process is a little inefficient so the bigger the source SD card, the bigger the backup will be and the longer it'll take to create (and even longer to image back to a new SD card), even if most of the card is blank.
|
||||
|
||||
You can restore a backup image to an SD card of the same or larger size. So it's strongly recommended to do repetitive development work on a smaller card that's easier to backup and restore. Once the image is stabilized, then write it to a bigger card, if necessary (that being said, there's really no reason to use a large SD card for SeedSigner. An 8GB SD card is more than big enough).
|
||||
|
||||
Insert the SD card into a Mac/Linux machine and create a compressed img file.
|
||||
|
||||
First verify the name of your SD card:
|
||||
|
||||
```
|
||||
# Mac:
|
||||
diskutil list
|
||||
|
||||
# Linux:
|
||||
sudo fdisk -l
|
||||
```
|
||||
|
||||
It will most likely be `/dev/disk1` on most systems.
|
||||
|
||||
Now we use `dd` to clone and `gzip` to compress it. Note that we reference the SD card by adding an `r` in front of the disk name. This speeds up the cloning considerably.
|
||||
|
||||
```
|
||||
sudo dd if=/dev/rdisk1 conv=sparse bs=4m | gzip -9 > seedsigner.img.gz
|
||||
```
|
||||
|
||||
The process should take about 15 minutes and will typically generate a roughly 1.1GB image.
|
||||
|
||||
To restore from your backup image, just use the Raspberry Pi Imager. Remember that you can only write it to an SD card of equal or greater size than the original SD card.
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
# Manual Installation Instructions
|
||||
|
||||
Begin by acquiring a specific copy of the Raspberry Pi Lite operating system, dated 2021-05-28; this version can be found here:
|
||||
|
||||
https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/
|
||||
|
||||
Best practice is to verify the downloaded .zip file containing the Raspberry Pi Lite OS matches the published SHA256 hash of the file; for additional reference that hash is: c5dad159a2775c687e9281b1a0e586f7471690ae28f2f2282c90e7d59f64273c. After verifying the file's data integrity, you can decompress the .zip file to obtain the operating system image that it contains. You can then use Balena's Etcher tool (https://www.balena.io/etcher/) to write the Raspberry Pi Lite software image to a memory card (4 GB or larger). It's important to note that an image authoring tool must be used (the operating system image cannot be simply copied into a file storage partition on the memory card).
|
||||
|
||||
The manual SeedSigner installation and configuration process requires an internet connection on the device to download the necessary libraries and code. But because the Pi Zero 1.3 does not have onboard wifi, you have two options:
|
||||
|
||||
1. Run these steps on a separate Raspberry Pi 2/3/4 or Zero W which can connect to the internet and then transfer the SD card to the Pi Zero 1.3 when complete.
|
||||
2. OR configure the Pi Zero 1.3 directly by relaying through your computer's internet connection over USB. See instructions [here](usb_relay.md).
|
||||
|
||||
For the following steps you'll need to either connect a keyboard & monitor to the network-connected Raspberry Pi you are working with, or SSH into the Pi if you're familiar with that process.
|
||||
|
||||
### Configure the Pi
|
||||
First things first, verify that you are using the correct version of the Raspberry Pi Lite operating system by typing the command:
|
||||
```
|
||||
cat /etc/os-release
|
||||
```
|
||||
The output of this command should match the following text:
|
||||
```
|
||||
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
|
||||
NAME="Raspbian GNU/Linux"
|
||||
VERSION_ID="10"
|
||||
VERSION="10 (buster)"
|
||||
VERSION_CODENAME=buster
|
||||
ID=raspbian
|
||||
ID_LIKE=debian
|
||||
HOME_URL="http://www.raspbian.org/"
|
||||
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
|
||||
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
|
||||
```
|
||||
|
||||
Now launch the Raspberry Pi's System Configuration tool using the command:
|
||||
```
|
||||
sudo raspi-config
|
||||
```
|
||||
|
||||
Set the following:
|
||||
* `Interface Options`:
|
||||
* `Camera`: enable
|
||||
* `SPI`: enable
|
||||
* `Localisation Options`:
|
||||
* `Locale`: arrow up and down through the list and select or deselect languages with the spacebar.
|
||||
* Deselect the default language option that is selected
|
||||
* Select `en_US.UTF-8 UTF-8` for US English
|
||||
* You will also need to configure the WiFi settings if you are using the #1 option above to connect to the internet
|
||||
|
||||
When you exit the System Configuration tool, you will be prompted to reboot the system; allow the system to reboot and continue with these instructions.
|
||||
|
||||
|
||||
### Change the default password
|
||||
Change the system's password from the default "raspberry". Do do this, use the command:
|
||||
```
|
||||
passwd
|
||||
```
|
||||
You will be prompted to enter the current password (raspberry) and then to enter a new password twice. In our prepared release image, the password used is `AirG@pped!` (without quotes).
|
||||
|
||||
|
||||
### Install dependencies
|
||||
```
|
||||
sudo apt-get update && sudo apt-get install -y wiringpi python3-pip python3-numpy python-pil libopenjp2-7 git python3-opencv python3-picamera libatlas-base-dev qrencode
|
||||
```
|
||||
|
||||
### Install `zbar`
|
||||
v0.4.6 requires `zbar` at 0.23.x or higher.
|
||||
|
||||
see: [https://github.com/mchehab/zbar](https://github.com/mchehab/zbar)
|
||||
|
||||
#### Get the `zbar` binary
|
||||
```
|
||||
curl -L http://raspbian.raspberrypi.org/raspbian/pool/main/z/zbar/libzbar0_0.23.90-1_armhf.deb --output libzbar0_0.23.90-1_armhf.deb
|
||||
sudo apt install ./libzbar0_0.23.90-1_armhf.deb
|
||||
```
|
||||
|
||||
#### Or install `zbar` by compiling from source
|
||||
This step isn't necessary. It's only if you prefer to compile `zbar` from source. The whole process takes about 30 minutes.
|
||||
```
|
||||
sudo apt-get install -y autopoint
|
||||
|
||||
cd ~/
|
||||
git clone https://github.com/mchehab/zbar.git
|
||||
cd zbar
|
||||
git checkout 0.23.90
|
||||
```
|
||||
|
||||
Configure the compiler settings and compile.
|
||||
```
|
||||
autoreconf -vfi && ./configure
|
||||
make
|
||||
make check
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Install the [C library for Broadcom BCM 2835](http://www.airspayce.com/mikem/bcm2835/)
|
||||
```
|
||||
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.60.tar.gz
|
||||
tar zxvf bcm2835-1.60.tar.gz
|
||||
cd bcm2835-1.60/
|
||||
sudo ./configure
|
||||
sudo make && sudo make check && sudo make install
|
||||
cd ..
|
||||
rm bcm2835-1.60.tar.gz
|
||||
```
|
||||
|
||||
### Set up `virtualenv`
|
||||
```
|
||||
pip3 install virtualenvwrapper
|
||||
```
|
||||
|
||||
Edit your bash profile with the command `nano ~/.profile` and add the following to the end:
|
||||
```
|
||||
export WORKON_HOME=$HOME/.envs
|
||||
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
|
||||
source /home/pi/.local/bin/virtualenvwrapper.sh
|
||||
```
|
||||
Then `CTRL-X` and `y` to exit and save changes.
|
||||
|
||||
Now create the python virtualenv for SeedSigner with these two commands:
|
||||
```
|
||||
source ~/.profile
|
||||
mkvirtualenv --python=python3 seedsigner-env
|
||||
cd seedsigner
|
||||
setvirtualenvproject
|
||||
```
|
||||
|
||||
For convenience you can configure your `.profile` to auto-activate the SeedSigner virtualenv when you ssh in. Once again `nano ~/.profile` and add at the end:
|
||||
```
|
||||
workon seedsigner-env
|
||||
```
|
||||
|
||||
Optional: If you're going to be testing new code on the SeedSigner, you'll find yourself often needing to kill the SeedSigner code that automatically runs at startup (we'll be configuring this further down). As an extra convenience you can list the process id so that you can then kill it from the terminal:
|
||||
```
|
||||
ps aux | grep main.py
|
||||
```
|
||||
|
||||
Save your changes with `CTRL-X` and `y`.
|
||||
|
||||
Now when you `ssh` in you'll see something like:
|
||||
```
|
||||
pi 297 65.4 9.7 74096 36736 ? Rsl 09:26 10:29 /home/pi/.envs/seedsigner-env/bin/python main.py
|
||||
pi 857 0.0 0.4 7332 1876 pts/0 S+ 09:42 0:00 grep --color=auto main.py
|
||||
```
|
||||
|
||||
The top line is our SeedSigner code running. To stop it, run:
|
||||
```
|
||||
kill 297
|
||||
```
|
||||
Where `297` is the process id listed in the output above (it'll be different each time).
|
||||
|
||||
|
||||
### Download the SeedSigner code:
|
||||
```
|
||||
git clone https://github.com/SeedSigner/seedsigner
|
||||
cd seedsigner
|
||||
```
|
||||
|
||||
If you want to run a specific branch within the main SeedSigner repo, switch to it with:
|
||||
```
|
||||
git checkout yourtargetbranch
|
||||
```
|
||||
|
||||
And if you want to test a pull request (PR), for example PR #123:
|
||||
```
|
||||
git fetch origin pull/123/head:pr_123
|
||||
git checkout pr_123
|
||||
```
|
||||
|
||||
where `pr_123` is any name you want to give to the new branch in your local repo that will hold the PR.
|
||||
|
||||
|
||||
### Install Python `pip` dependencies:
|
||||
```
|
||||
pip3 install -r requirements.txt
|
||||
cd ..
|
||||
```
|
||||
|
||||
#### `pyzbar`
|
||||
Note: The `requirements.txt` installs a fork of the python `pyzbar` repo (for now pointing to the fork in Keith's `kdmukai` github account [https://github.com/kdmukai/pyzbar](https://github.com/kdmukai/pyzbar)).
|
||||
|
||||
The fork is required because the main `pyzbar` repo has been abandoned. This [github issue](https://github.com/NaturalHistoryMuseum/pyzbar/issues/124#issuecomment-971967091) discusses the changes needed in order to support reading binary data from `zbar`, which is required for our `CompactSeedQR` format which writes byte data instead of strings. The changes specifically reference the following PRs which have already been merged into Keith's fork:
|
||||
* [PR 76](https://github.com/NaturalHistoryMuseum/pyzbar/pull/76/files): enables scanning to continue even when a null byte (`x\00`) is found.
|
||||
* [PR 82](https://github.com/NaturalHistoryMuseum/pyzbar/pull/82): enable `zbar`'s new binary mode. Note that this PR has a trivial bug that was fixed in Keith's fork.
|
||||
|
||||
### Configure `systemd` to run SeedSigner at boot:
|
||||
```
|
||||
sudo nano /etc/systemd/system/seedsigner.service
|
||||
```
|
||||
|
||||
Add the following contents to the text file that was created:
|
||||
```
|
||||
[Unit]
|
||||
Description=Seedsigner
|
||||
|
||||
[Service]
|
||||
User=pi
|
||||
WorkingDirectory=/home/pi/seedsigner/src/
|
||||
ExecStart=/home/pi/.envs/seedsigner-env/bin/python main.py
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
_Note: If you'll be testing new code on the SeedSigner, you'll want to omit the `Restart=always` line._
|
||||
|
||||
Use `CTRL-X` and `y` to exit and save changes.
|
||||
|
||||
Run `sudo systemctl enable seedsigner.service` to enable service on boot. (This will restart the seedsigner code automatically at startup and if it crashes.)
|
||||
|
||||
Now reboot the Raspberry Pi with the command:
|
||||
```
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
After the Raspberry Pi reboots, you should see the SeedSigner splash screen and the SeedSigner menu subsequently appear on the LCD screen (note that it can take up to 60 seconds for the menu to appear).
|
||||
|
||||
### Further OS modifications
|
||||
Disable and remove the system's virtual memory / swap file with the commands:
|
||||
```
|
||||
sudo apt remove dphys-swapfile -y
|
||||
sudo apt autoremove -y
|
||||
sudo rm /var/swap
|
||||
```
|
||||
|
||||
## Local testing and development
|
||||
For those who will use the SeedSigner installation for testing/development, it can be helpful to change the system's host name so it doesn't potentially conflict with other Raspberry Pis that may already be present on your network. (For those who don't plan to use the installation for testing or development, you can skip this portion of the process.) To change the host name first edit the "hostname" with the command:
|
||||
```
|
||||
sudo nano /etc/hostname
|
||||
```
|
||||
and change "raspberrypi" to "seedsigner" (or another name). Use `CTRL-X` and `y` to exit and save changes. You'll also need to edit the "hosts" file with the command:
|
||||
```
|
||||
sudo nano /etc/hosts
|
||||
```
|
||||
and change "raspberrypi" to "seedsigner" (or the other name you previously chose). Use `CTRL-X` and `y` to exit and save changes.
|
||||
|
||||
### Set a static IP
|
||||
Your local machine that `ssh`s into the SeedSigner can sometimes get confused if you're connecting to different SeedSigners that are all identified as `pi@seedsigner.local`. In this case it helps to set a static ip and just `ssh` directly to that instead.
|
||||
|
||||
First find your current `nameserver`:
|
||||
```
|
||||
sudo cat /etc/resolv.conf
|
||||
```
|
||||
|
||||
This is the address of your local machine that is connected to your SeedSigner via usb (or it'll be the wifi router's address if you're using a Raspi with wifi and are keeping it enabled for `ssh` access).
|
||||
|
||||
Set a static ip: `sudo nano /etc/dhcpcd.conf` and add to the end:
|
||||
```
|
||||
interface usb0
|
||||
static ip_address=192.168.1.200/24
|
||||
static routers=192.168.1.254
|
||||
static domain_name_servers=192.168.1.254
|
||||
```
|
||||
|
||||
* `interface` will be `usb0` for usb connections; `wlan0` for wifi.
|
||||
* `static ip_address` is the ip address you want the SeedSigner to use. It should match the `nameserver` ip you found above for all but the last part of the ip (note: the `/24` should always be included as-is).
|
||||
* `static routers` should be your `nameserver` ip.
|
||||
* `static domain_name_servers` should also be the `nameserver` ip.
|
||||
|
||||
`CTRL-X` and `y` to save changes.
|
||||
|
||||
After your next reboot, access this SeedSigner using its new static ip:
|
||||
```
|
||||
ssh pi@192.168.1.200
|
||||
```
|
||||
|
||||
### More convenient `ssh` access:
|
||||
Power SeedSigner devs will find themselves connecting to a lot of different SeedSigners. This can cause headaches with `ssh`'s built-in protections; a different device that uses the same `ssh` credentials is normally a potential spoofing attack. But we're doing this to ourselves on purpose and so we can carve out exceptions.
|
||||
|
||||
On your local machine, run `nano ~/.ssh/config` and add to the end:
|
||||
```
|
||||
host seedsigner.local
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
User pi
|
||||
LogLevel QUIET
|
||||
|
||||
host 192.168.1.200
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
User pi
|
||||
LogLevel QUIET
|
||||
```
|
||||
|
||||
The first entry prevents warnings for the default `pi@seedsigner.local` connections.
|
||||
|
||||
The second entry does the same for a specific static ip; you'll want this if you configure all your SeedSigners to use the same static ip.
|
||||
|
||||
`CTRL-X` and `y` to save changes.
|
||||
|
||||
|
||||
#### Bypass `ssh` password
|
||||
You can also configure the SeedSigner so that you don't have to enter the `pi` password when you `ssh` in.
|
||||
|
||||
run `ssh-copy-id` with the same values that you connect via `ssh`:
|
||||
```
|
||||
ssh-copy-id pi@seedsigner.local
|
||||
|
||||
# or if you're connecting over static ip, something like:
|
||||
ssh-copy-id pi@192.168.1.200
|
||||
```
|
||||
|
||||
You'll be prompted to enter the password to complete it.
|
||||
|
||||
_Note: If you don't have any ssh keys on your local machine, you'll need to create a set with `ssh-keygen -t ed25519 -C "your_email@example.com"`. Then try running `ssh-copy-id` again._
|
||||
|
||||
|
||||
## Disable wifi/Bluetooth when using other Raspi boards
|
||||
If you plan to use your installation on a Raspberry Pi that is not a Zero version 1.3, but rather on a Raspberry Pi that has WiFi and Bluetooth capabilities, it is a good idea to disable the following WiFi & Bluetooth, as well as other relevant services (assuming you are not creating this installation for testing/development purposes). Enter the followiing commands to disable WiFi, Bluetooth, & other relevant services:
|
||||
```
|
||||
sudo systemctl disable bluetooth.service
|
||||
sudo systemctl disable wpa_supplicant.service
|
||||
sudo systemctl disable dhcpcd.service
|
||||
sudo systemctl disable sshd.service
|
||||
sudo systemctl disable networking.service
|
||||
sudo systemctl disable dphys-swapfile.service
|
||||
sudo ifconfig wlan0 down
|
||||
```
|
||||
Please note that if you are using WiFi to connect/interact with your Raspberry Pi, the last command will sever that connection.
|
||||
|
||||
You can now safely power the Raspberry Pi off from the SeedSigner main menu.
|
||||
|
||||
If you do not plan to use your installation for testing/development, it is also a good idea to disable WiFi and Bluetooth by editing the config.txt file found in the installation's "boot" partition. You can add the following text to the end of that file with any simple text editor (Windows: Notepad, Mac: TextEdit, Linux: nano):
|
||||
```
|
||||
dtoverlay=disable-bt
|
||||
dtoverlay=pi3-disable-wifi
|
||||
```
|
||||
|
||||
If you used option #2 above and don't plan to continue to access your SeedSigner via SSH over USB, it is a good idea to reverse the steps you took to enable it -- those instructions can be found near the end of [this guide](usb_relay.md).
|
||||
|
||||
Please remember that it can take up to a minute for the GUI to appear when powering your SeedSigner on.
|
||||
|
||||
|
||||
### Optional: Run the tests
|
||||
see: [tests/README.md](../tests/README.md)
|
||||
@@ -1,9 +1,11 @@
|
||||
import math
|
||||
|
||||
def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=False):
|
||||
if num_words == 24:
|
||||
def generate_qr_template(qr_size, block_size=5, show_timing_marks=False):
|
||||
if qr_size == 29:
|
||||
num_words = 24
|
||||
word_cols = 2
|
||||
else:
|
||||
num_words = 12
|
||||
word_cols = 1
|
||||
|
||||
html = """
|
||||
@@ -31,24 +33,18 @@ def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=
|
||||
td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 12px;
|
||||
border: 1px dotted #c8c8c8;
|
||||
width: 11px;
|
||||
height: 12px;
|
||||
}
|
||||
.qrcell {
|
||||
text-align: center;
|
||||
font-size: 0.35em;
|
||||
color: black;
|
||||
width: 13px;
|
||||
height: 9px;
|
||||
}
|
||||
.qr_table {
|
||||
margin-left: 4em;
|
||||
display: inline-block;
|
||||
margin-top: 5em;
|
||||
margin-right: 4em;
|
||||
float: right;
|
||||
}
|
||||
.filled {
|
||||
background-color: black;
|
||||
border: none;
|
||||
color: white;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.col_name, .row_name {
|
||||
text-align: center;
|
||||
@@ -56,19 +52,13 @@ def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=
|
||||
color: #aaa;
|
||||
}
|
||||
.col_name {
|
||||
border-top: 1px solid #bbb;
|
||||
border-bottom: 1px solid #bbb;
|
||||
height: 2em;
|
||||
}
|
||||
.row_name {
|
||||
border-right: 1px solid #bbb;
|
||||
border-left: 1px solid #bbb;
|
||||
width: 2em;
|
||||
}
|
||||
.first_td {
|
||||
border-top: 1px solid #bbb;
|
||||
border-left: 1px solid #bbb;
|
||||
}
|
||||
.col_block_divider {
|
||||
border-right: 1px solid #bbb;
|
||||
}
|
||||
@@ -76,11 +66,10 @@ def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=
|
||||
border-bottom: 1px solid #bbb;
|
||||
}
|
||||
.no_border {
|
||||
border: none !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.word_list_table {
|
||||
border: 1px solid #aaa;
|
||||
"""
|
||||
|
||||
if num_words == 12:
|
||||
@@ -92,7 +81,6 @@ def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=
|
||||
float: left;
|
||||
}
|
||||
.word_list {
|
||||
font-size: 0.5em;
|
||||
text-align: right;
|
||||
padding-top: 2em;
|
||||
padding-left: 1em;
|
||||
@@ -133,7 +121,7 @@ def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=
|
||||
y_names = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,X,Y,Z"
|
||||
|
||||
html += """<table align="center" class="qr_table">"""
|
||||
html += f"""<tr rowspan="{block_size}"><td class="col_block_divider row_block_divider first_td"></td>"""
|
||||
html += f"""<tr rowspan="{block_size}"><td class="col_block_divider row_block_divider"></td>"""
|
||||
for j in range(0, math.ceil(qr_size/block_size)):
|
||||
html += f"""<td colspan="{block_size}" class="col_name col_block_divider">{j + 1}</td>"""
|
||||
html += "</tr>"
|
||||
@@ -141,46 +129,37 @@ def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=
|
||||
for i in range(0, qr_size):
|
||||
html += """<tr>\n"""
|
||||
if i % block_size == 0:
|
||||
html += f"""<td rowspan="{block_size}" class="row_name row_block_divider qrcell">{y_names.split(",")[int(i / block_size)]}</td>"""
|
||||
html += f"""<td rowspan="{block_size}" class="row_name row_block_divider">{y_names.split(",")[int(i / block_size)]}</td>"""
|
||||
for j in range(0, qr_size):
|
||||
html += f"""<td class="qrcell {"row_block_divider" if i == qr_size - 1 else ""} {"col_block_divider" if j == qr_size -1 else ""}" id="{i}_{j}">·</td>"""
|
||||
html += f"""<td id="{i}_{j}"></td>"""
|
||||
html += """</tr>\n"""
|
||||
html += "</table>"
|
||||
|
||||
html += "<script>"
|
||||
|
||||
def fill(i, j, clear_contents=True):
|
||||
js_result = f"""document.getElementById("{i}_{j}").classList.add("filled");\n"""
|
||||
if clear_contents:
|
||||
js_result += f"""document.getElementById("{i}_{j}").textContent = '';\n"""
|
||||
return js_result
|
||||
def fill(i, j):
|
||||
return f"""document.getElementById("{i}_{j}").classList.add("filled");\n"""
|
||||
|
||||
def no_border(i, j):
|
||||
js_result = f"""document.getElementById("{i}_{j}").classList.remove("row_block_divider");\n"""
|
||||
js_result += f"""document.getElementById("{i}_{j}").classList.remove("col_block_divider");\n"""
|
||||
js_result += f"""document.getElementById("{i}_{j}").classList.add("no_border");\n"""
|
||||
return js_result
|
||||
return f"""document.getElementById("{i}_{j}").classList.add("no_border");\n"""
|
||||
|
||||
|
||||
# Generate the corner registration box
|
||||
def fill_registration_box(offset_i, offset_j):
|
||||
result = ""
|
||||
for i in range(0, 7):
|
||||
clear_middot = i != 0 and i != 6
|
||||
i += offset_i
|
||||
result += fill(i, offset_j + 0, clear_contents=clear_middot)
|
||||
result += fill(i, offset_j + 6, clear_contents=clear_middot)
|
||||
result += fill(i, offset_j + 0)
|
||||
result += fill(i, offset_j + 6)
|
||||
for j in range(1, 6):
|
||||
j += offset_j
|
||||
result += fill (offset_i + 0, j)
|
||||
result += fill (offset_i + 6, j)
|
||||
for i in range(2, 5):
|
||||
clear_middot = i != 2 and i != 4
|
||||
i += offset_i
|
||||
result += fill(i, offset_j + 2, clear_contents=clear_middot)
|
||||
result += fill(i, offset_j + 2)
|
||||
result += fill(i, offset_j + 3)
|
||||
result += fill(i, offset_j + 4, clear_contents=clear_middot)
|
||||
|
||||
result += fill(i, offset_j + 4)
|
||||
for i in range(offset_i, offset_i + 7):
|
||||
for j in range(offset_j, offset_j + 7):
|
||||
result += no_border(i, j)
|
||||
@@ -214,13 +193,11 @@ def generate_qr_template(qr_size, num_words=24, block_size=5, show_timing_marks=
|
||||
|
||||
if qr_size > 21:
|
||||
# Fill the smaller inset registration box
|
||||
html += fill(qr_size - 7, qr_size - 7, clear_contents=False)
|
||||
html += fill(qr_size - 7, qr_size - 7)
|
||||
|
||||
# fill the sides
|
||||
for i in range(qr_size - 9, qr_size - 4):
|
||||
clear_middot = i != (qr_size - 9) and i != (qr_size - 5)
|
||||
html += fill(i, qr_size - 9, clear_contents=clear_middot)
|
||||
html += fill(i, qr_size - 5, clear_contents=clear_middot)
|
||||
html += fill(i, qr_size - 9)
|
||||
html += fill(i, qr_size - 5)
|
||||
for j in range(qr_size - 8, qr_size - 5):
|
||||
html += fill(qr_size - 9, j)
|
||||
html += fill(qr_size - 5, j)
|
||||
@@ -256,12 +233,12 @@ if __name__ == "__main__":
|
||||
description="""
|
||||
Generates a blank QR code template at 21x21, 25x25, or 29x29.
|
||||
|
||||
ex: python3 qr_code_template.py 25 12
|
||||
ex: python3 qr_code_template.py 29 24
|
||||
ex: python3 qr_code_template.py 25
|
||||
ex: python3 qr_code_template.py 29
|
||||
|
||||
Optionally change the block size divider guides:
|
||||
|
||||
python3 qr_code_template.py 29 24 --block_size 6
|
||||
python3 qr_code_template.py 29 --block_size 6
|
||||
""",
|
||||
formatter_class=argparse.RawTextHelpFormatter
|
||||
)
|
||||
@@ -271,12 +248,6 @@ if __name__ == "__main__":
|
||||
type=int,
|
||||
choices=[21, 25, 29],
|
||||
help="QR code size. 21 for 21x21, 25 for 25x25, 29 for 29x29")
|
||||
parser.add_argument('num_words',
|
||||
type=int,
|
||||
choices=[12, 24],
|
||||
help="Number of words in the mnemonic seed. 12 or 24")
|
||||
|
||||
# Add optional arguments
|
||||
parser.add_argument('-b', '--block_size',
|
||||
type=int,
|
||||
default=5,
|
||||
@@ -287,8 +258,8 @@ if __name__ == "__main__":
|
||||
help="Show timing marks (the dashed blocks linking the three large registration boxes")
|
||||
args = parser.parse_args()
|
||||
|
||||
html = generate_qr_template(qr_size=args.qr_size, num_words=args.num_words, block_size=args.block_size, show_timing_marks=args.timing_marks)
|
||||
file = open(f"{args.num_words}words_seedqr_template-{args.qr_size}x{args.qr_size}.html", "w")
|
||||
html = generate_qr_template(qr_size=args.qr_size, block_size=args.block_size, show_timing_marks=args.timing_marks)
|
||||
file = open(f"qr_code_template_{args.qr_size}x{args.qr_size}.html", "w")
|
||||
file.write(html)
|
||||
file.close()
|
||||
|
||||
@@ -0,0 +1,515 @@
|
||||
|
||||
<html>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
.title {
|
||||
margin-top: 0.5em;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 1em
|
||||
}
|
||||
table {
|
||||
margin-top: 1em;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px dotted #c8c8c8;
|
||||
width: 11px;
|
||||
height: 12px;
|
||||
}
|
||||
.filled {
|
||||
background-color: black;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.col_name, .row_name {
|
||||
text-align: center;
|
||||
font-size: 0.7em;
|
||||
color: #aaa;
|
||||
}
|
||||
.col_name {
|
||||
border-bottom: 1px solid #bbb;
|
||||
height: 2em;
|
||||
}
|
||||
.row_name {
|
||||
border-right: 1px solid #bbb;
|
||||
width: 2em;
|
||||
}
|
||||
.col_block_divider {
|
||||
border-right: 1px solid #bbb;
|
||||
}
|
||||
.row_block_divider {
|
||||
border-bottom: 1px solid #bbb;
|
||||
}
|
||||
.no_border {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="title">21x21 QR Code</div><table align="center"><tr rowspan="5"><td class="col_block_divider row_block_divider"></td><td colspan="5" class="col_name col_block_divider">1</td><td colspan="5" class="col_name col_block_divider">2</td><td colspan="5" class="col_name col_block_divider">3</td><td colspan="5" class="col_name col_block_divider">4</td><td colspan="5" class="col_name col_block_divider">5</td></tr><tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">A</td><td id="0_0"></td><td id="0_1"></td><td id="0_2"></td><td id="0_3"></td><td id="0_4"></td><td id="0_5"></td><td id="0_6"></td><td id="0_7"></td><td id="0_8"></td><td id="0_9"></td><td id="0_10"></td><td id="0_11"></td><td id="0_12"></td><td id="0_13"></td><td id="0_14"></td><td id="0_15"></td><td id="0_16"></td><td id="0_17"></td><td id="0_18"></td><td id="0_19"></td><td id="0_20"></td></tr>
|
||||
<tr>
|
||||
<td id="1_0"></td><td id="1_1"></td><td id="1_2"></td><td id="1_3"></td><td id="1_4"></td><td id="1_5"></td><td id="1_6"></td><td id="1_7"></td><td id="1_8"></td><td id="1_9"></td><td id="1_10"></td><td id="1_11"></td><td id="1_12"></td><td id="1_13"></td><td id="1_14"></td><td id="1_15"></td><td id="1_16"></td><td id="1_17"></td><td id="1_18"></td><td id="1_19"></td><td id="1_20"></td></tr>
|
||||
<tr>
|
||||
<td id="2_0"></td><td id="2_1"></td><td id="2_2"></td><td id="2_3"></td><td id="2_4"></td><td id="2_5"></td><td id="2_6"></td><td id="2_7"></td><td id="2_8"></td><td id="2_9"></td><td id="2_10"></td><td id="2_11"></td><td id="2_12"></td><td id="2_13"></td><td id="2_14"></td><td id="2_15"></td><td id="2_16"></td><td id="2_17"></td><td id="2_18"></td><td id="2_19"></td><td id="2_20"></td></tr>
|
||||
<tr>
|
||||
<td id="3_0"></td><td id="3_1"></td><td id="3_2"></td><td id="3_3"></td><td id="3_4"></td><td id="3_5"></td><td id="3_6"></td><td id="3_7"></td><td id="3_8"></td><td id="3_9"></td><td id="3_10"></td><td id="3_11"></td><td id="3_12"></td><td id="3_13"></td><td id="3_14"></td><td id="3_15"></td><td id="3_16"></td><td id="3_17"></td><td id="3_18"></td><td id="3_19"></td><td id="3_20"></td></tr>
|
||||
<tr>
|
||||
<td id="4_0"></td><td id="4_1"></td><td id="4_2"></td><td id="4_3"></td><td id="4_4"></td><td id="4_5"></td><td id="4_6"></td><td id="4_7"></td><td id="4_8"></td><td id="4_9"></td><td id="4_10"></td><td id="4_11"></td><td id="4_12"></td><td id="4_13"></td><td id="4_14"></td><td id="4_15"></td><td id="4_16"></td><td id="4_17"></td><td id="4_18"></td><td id="4_19"></td><td id="4_20"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">B</td><td id="5_0"></td><td id="5_1"></td><td id="5_2"></td><td id="5_3"></td><td id="5_4"></td><td id="5_5"></td><td id="5_6"></td><td id="5_7"></td><td id="5_8"></td><td id="5_9"></td><td id="5_10"></td><td id="5_11"></td><td id="5_12"></td><td id="5_13"></td><td id="5_14"></td><td id="5_15"></td><td id="5_16"></td><td id="5_17"></td><td id="5_18"></td><td id="5_19"></td><td id="5_20"></td></tr>
|
||||
<tr>
|
||||
<td id="6_0"></td><td id="6_1"></td><td id="6_2"></td><td id="6_3"></td><td id="6_4"></td><td id="6_5"></td><td id="6_6"></td><td id="6_7"></td><td id="6_8"></td><td id="6_9"></td><td id="6_10"></td><td id="6_11"></td><td id="6_12"></td><td id="6_13"></td><td id="6_14"></td><td id="6_15"></td><td id="6_16"></td><td id="6_17"></td><td id="6_18"></td><td id="6_19"></td><td id="6_20"></td></tr>
|
||||
<tr>
|
||||
<td id="7_0"></td><td id="7_1"></td><td id="7_2"></td><td id="7_3"></td><td id="7_4"></td><td id="7_5"></td><td id="7_6"></td><td id="7_7"></td><td id="7_8"></td><td id="7_9"></td><td id="7_10"></td><td id="7_11"></td><td id="7_12"></td><td id="7_13"></td><td id="7_14"></td><td id="7_15"></td><td id="7_16"></td><td id="7_17"></td><td id="7_18"></td><td id="7_19"></td><td id="7_20"></td></tr>
|
||||
<tr>
|
||||
<td id="8_0"></td><td id="8_1"></td><td id="8_2"></td><td id="8_3"></td><td id="8_4"></td><td id="8_5"></td><td id="8_6"></td><td id="8_7"></td><td id="8_8"></td><td id="8_9"></td><td id="8_10"></td><td id="8_11"></td><td id="8_12"></td><td id="8_13"></td><td id="8_14"></td><td id="8_15"></td><td id="8_16"></td><td id="8_17"></td><td id="8_18"></td><td id="8_19"></td><td id="8_20"></td></tr>
|
||||
<tr>
|
||||
<td id="9_0"></td><td id="9_1"></td><td id="9_2"></td><td id="9_3"></td><td id="9_4"></td><td id="9_5"></td><td id="9_6"></td><td id="9_7"></td><td id="9_8"></td><td id="9_9"></td><td id="9_10"></td><td id="9_11"></td><td id="9_12"></td><td id="9_13"></td><td id="9_14"></td><td id="9_15"></td><td id="9_16"></td><td id="9_17"></td><td id="9_18"></td><td id="9_19"></td><td id="9_20"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">C</td><td id="10_0"></td><td id="10_1"></td><td id="10_2"></td><td id="10_3"></td><td id="10_4"></td><td id="10_5"></td><td id="10_6"></td><td id="10_7"></td><td id="10_8"></td><td id="10_9"></td><td id="10_10"></td><td id="10_11"></td><td id="10_12"></td><td id="10_13"></td><td id="10_14"></td><td id="10_15"></td><td id="10_16"></td><td id="10_17"></td><td id="10_18"></td><td id="10_19"></td><td id="10_20"></td></tr>
|
||||
<tr>
|
||||
<td id="11_0"></td><td id="11_1"></td><td id="11_2"></td><td id="11_3"></td><td id="11_4"></td><td id="11_5"></td><td id="11_6"></td><td id="11_7"></td><td id="11_8"></td><td id="11_9"></td><td id="11_10"></td><td id="11_11"></td><td id="11_12"></td><td id="11_13"></td><td id="11_14"></td><td id="11_15"></td><td id="11_16"></td><td id="11_17"></td><td id="11_18"></td><td id="11_19"></td><td id="11_20"></td></tr>
|
||||
<tr>
|
||||
<td id="12_0"></td><td id="12_1"></td><td id="12_2"></td><td id="12_3"></td><td id="12_4"></td><td id="12_5"></td><td id="12_6"></td><td id="12_7"></td><td id="12_8"></td><td id="12_9"></td><td id="12_10"></td><td id="12_11"></td><td id="12_12"></td><td id="12_13"></td><td id="12_14"></td><td id="12_15"></td><td id="12_16"></td><td id="12_17"></td><td id="12_18"></td><td id="12_19"></td><td id="12_20"></td></tr>
|
||||
<tr>
|
||||
<td id="13_0"></td><td id="13_1"></td><td id="13_2"></td><td id="13_3"></td><td id="13_4"></td><td id="13_5"></td><td id="13_6"></td><td id="13_7"></td><td id="13_8"></td><td id="13_9"></td><td id="13_10"></td><td id="13_11"></td><td id="13_12"></td><td id="13_13"></td><td id="13_14"></td><td id="13_15"></td><td id="13_16"></td><td id="13_17"></td><td id="13_18"></td><td id="13_19"></td><td id="13_20"></td></tr>
|
||||
<tr>
|
||||
<td id="14_0"></td><td id="14_1"></td><td id="14_2"></td><td id="14_3"></td><td id="14_4"></td><td id="14_5"></td><td id="14_6"></td><td id="14_7"></td><td id="14_8"></td><td id="14_9"></td><td id="14_10"></td><td id="14_11"></td><td id="14_12"></td><td id="14_13"></td><td id="14_14"></td><td id="14_15"></td><td id="14_16"></td><td id="14_17"></td><td id="14_18"></td><td id="14_19"></td><td id="14_20"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">D</td><td id="15_0"></td><td id="15_1"></td><td id="15_2"></td><td id="15_3"></td><td id="15_4"></td><td id="15_5"></td><td id="15_6"></td><td id="15_7"></td><td id="15_8"></td><td id="15_9"></td><td id="15_10"></td><td id="15_11"></td><td id="15_12"></td><td id="15_13"></td><td id="15_14"></td><td id="15_15"></td><td id="15_16"></td><td id="15_17"></td><td id="15_18"></td><td id="15_19"></td><td id="15_20"></td></tr>
|
||||
<tr>
|
||||
<td id="16_0"></td><td id="16_1"></td><td id="16_2"></td><td id="16_3"></td><td id="16_4"></td><td id="16_5"></td><td id="16_6"></td><td id="16_7"></td><td id="16_8"></td><td id="16_9"></td><td id="16_10"></td><td id="16_11"></td><td id="16_12"></td><td id="16_13"></td><td id="16_14"></td><td id="16_15"></td><td id="16_16"></td><td id="16_17"></td><td id="16_18"></td><td id="16_19"></td><td id="16_20"></td></tr>
|
||||
<tr>
|
||||
<td id="17_0"></td><td id="17_1"></td><td id="17_2"></td><td id="17_3"></td><td id="17_4"></td><td id="17_5"></td><td id="17_6"></td><td id="17_7"></td><td id="17_8"></td><td id="17_9"></td><td id="17_10"></td><td id="17_11"></td><td id="17_12"></td><td id="17_13"></td><td id="17_14"></td><td id="17_15"></td><td id="17_16"></td><td id="17_17"></td><td id="17_18"></td><td id="17_19"></td><td id="17_20"></td></tr>
|
||||
<tr>
|
||||
<td id="18_0"></td><td id="18_1"></td><td id="18_2"></td><td id="18_3"></td><td id="18_4"></td><td id="18_5"></td><td id="18_6"></td><td id="18_7"></td><td id="18_8"></td><td id="18_9"></td><td id="18_10"></td><td id="18_11"></td><td id="18_12"></td><td id="18_13"></td><td id="18_14"></td><td id="18_15"></td><td id="18_16"></td><td id="18_17"></td><td id="18_18"></td><td id="18_19"></td><td id="18_20"></td></tr>
|
||||
<tr>
|
||||
<td id="19_0"></td><td id="19_1"></td><td id="19_2"></td><td id="19_3"></td><td id="19_4"></td><td id="19_5"></td><td id="19_6"></td><td id="19_7"></td><td id="19_8"></td><td id="19_9"></td><td id="19_10"></td><td id="19_11"></td><td id="19_12"></td><td id="19_13"></td><td id="19_14"></td><td id="19_15"></td><td id="19_16"></td><td id="19_17"></td><td id="19_18"></td><td id="19_19"></td><td id="19_20"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">E</td><td id="20_0"></td><td id="20_1"></td><td id="20_2"></td><td id="20_3"></td><td id="20_4"></td><td id="20_5"></td><td id="20_6"></td><td id="20_7"></td><td id="20_8"></td><td id="20_9"></td><td id="20_10"></td><td id="20_11"></td><td id="20_12"></td><td id="20_13"></td><td id="20_14"></td><td id="20_15"></td><td id="20_16"></td><td id="20_17"></td><td id="20_18"></td><td id="20_19"></td><td id="20_20"></td></tr>
|
||||
</table><script>document.getElementById("0_0").classList.add("filled");
|
||||
document.getElementById("0_6").classList.add("filled");
|
||||
document.getElementById("1_0").classList.add("filled");
|
||||
document.getElementById("1_6").classList.add("filled");
|
||||
document.getElementById("2_0").classList.add("filled");
|
||||
document.getElementById("2_6").classList.add("filled");
|
||||
document.getElementById("3_0").classList.add("filled");
|
||||
document.getElementById("3_6").classList.add("filled");
|
||||
document.getElementById("4_0").classList.add("filled");
|
||||
document.getElementById("4_6").classList.add("filled");
|
||||
document.getElementById("5_0").classList.add("filled");
|
||||
document.getElementById("5_6").classList.add("filled");
|
||||
document.getElementById("6_0").classList.add("filled");
|
||||
document.getElementById("6_6").classList.add("filled");
|
||||
document.getElementById("0_1").classList.add("filled");
|
||||
document.getElementById("6_1").classList.add("filled");
|
||||
document.getElementById("0_2").classList.add("filled");
|
||||
document.getElementById("6_2").classList.add("filled");
|
||||
document.getElementById("0_3").classList.add("filled");
|
||||
document.getElementById("6_3").classList.add("filled");
|
||||
document.getElementById("0_4").classList.add("filled");
|
||||
document.getElementById("6_4").classList.add("filled");
|
||||
document.getElementById("0_5").classList.add("filled");
|
||||
document.getElementById("6_5").classList.add("filled");
|
||||
document.getElementById("2_2").classList.add("filled");
|
||||
document.getElementById("2_3").classList.add("filled");
|
||||
document.getElementById("2_4").classList.add("filled");
|
||||
document.getElementById("3_2").classList.add("filled");
|
||||
document.getElementById("3_3").classList.add("filled");
|
||||
document.getElementById("3_4").classList.add("filled");
|
||||
document.getElementById("4_2").classList.add("filled");
|
||||
document.getElementById("4_3").classList.add("filled");
|
||||
document.getElementById("4_4").classList.add("filled");
|
||||
document.getElementById("0_0").classList.add("no_border");
|
||||
document.getElementById("0_1").classList.add("no_border");
|
||||
document.getElementById("0_2").classList.add("no_border");
|
||||
document.getElementById("0_3").classList.add("no_border");
|
||||
document.getElementById("0_4").classList.add("no_border");
|
||||
document.getElementById("0_5").classList.add("no_border");
|
||||
document.getElementById("0_6").classList.add("no_border");
|
||||
document.getElementById("1_0").classList.add("no_border");
|
||||
document.getElementById("1_1").classList.add("no_border");
|
||||
document.getElementById("1_2").classList.add("no_border");
|
||||
document.getElementById("1_3").classList.add("no_border");
|
||||
document.getElementById("1_4").classList.add("no_border");
|
||||
document.getElementById("1_5").classList.add("no_border");
|
||||
document.getElementById("1_6").classList.add("no_border");
|
||||
document.getElementById("2_0").classList.add("no_border");
|
||||
document.getElementById("2_1").classList.add("no_border");
|
||||
document.getElementById("2_2").classList.add("no_border");
|
||||
document.getElementById("2_3").classList.add("no_border");
|
||||
document.getElementById("2_4").classList.add("no_border");
|
||||
document.getElementById("2_5").classList.add("no_border");
|
||||
document.getElementById("2_6").classList.add("no_border");
|
||||
document.getElementById("3_0").classList.add("no_border");
|
||||
document.getElementById("3_1").classList.add("no_border");
|
||||
document.getElementById("3_2").classList.add("no_border");
|
||||
document.getElementById("3_3").classList.add("no_border");
|
||||
document.getElementById("3_4").classList.add("no_border");
|
||||
document.getElementById("3_5").classList.add("no_border");
|
||||
document.getElementById("3_6").classList.add("no_border");
|
||||
document.getElementById("4_0").classList.add("no_border");
|
||||
document.getElementById("4_1").classList.add("no_border");
|
||||
document.getElementById("4_2").classList.add("no_border");
|
||||
document.getElementById("4_3").classList.add("no_border");
|
||||
document.getElementById("4_4").classList.add("no_border");
|
||||
document.getElementById("4_5").classList.add("no_border");
|
||||
document.getElementById("4_6").classList.add("no_border");
|
||||
document.getElementById("5_0").classList.add("no_border");
|
||||
document.getElementById("5_1").classList.add("no_border");
|
||||
document.getElementById("5_2").classList.add("no_border");
|
||||
document.getElementById("5_3").classList.add("no_border");
|
||||
document.getElementById("5_4").classList.add("no_border");
|
||||
document.getElementById("5_5").classList.add("no_border");
|
||||
document.getElementById("5_6").classList.add("no_border");
|
||||
document.getElementById("6_0").classList.add("no_border");
|
||||
document.getElementById("6_1").classList.add("no_border");
|
||||
document.getElementById("6_2").classList.add("no_border");
|
||||
document.getElementById("6_3").classList.add("no_border");
|
||||
document.getElementById("6_4").classList.add("no_border");
|
||||
document.getElementById("6_5").classList.add("no_border");
|
||||
document.getElementById("6_6").classList.add("no_border");
|
||||
document.getElementById("0_14").classList.add("filled");
|
||||
document.getElementById("0_20").classList.add("filled");
|
||||
document.getElementById("1_14").classList.add("filled");
|
||||
document.getElementById("1_20").classList.add("filled");
|
||||
document.getElementById("2_14").classList.add("filled");
|
||||
document.getElementById("2_20").classList.add("filled");
|
||||
document.getElementById("3_14").classList.add("filled");
|
||||
document.getElementById("3_20").classList.add("filled");
|
||||
document.getElementById("4_14").classList.add("filled");
|
||||
document.getElementById("4_20").classList.add("filled");
|
||||
document.getElementById("5_14").classList.add("filled");
|
||||
document.getElementById("5_20").classList.add("filled");
|
||||
document.getElementById("6_14").classList.add("filled");
|
||||
document.getElementById("6_20").classList.add("filled");
|
||||
document.getElementById("0_15").classList.add("filled");
|
||||
document.getElementById("6_15").classList.add("filled");
|
||||
document.getElementById("0_16").classList.add("filled");
|
||||
document.getElementById("6_16").classList.add("filled");
|
||||
document.getElementById("0_17").classList.add("filled");
|
||||
document.getElementById("6_17").classList.add("filled");
|
||||
document.getElementById("0_18").classList.add("filled");
|
||||
document.getElementById("6_18").classList.add("filled");
|
||||
document.getElementById("0_19").classList.add("filled");
|
||||
document.getElementById("6_19").classList.add("filled");
|
||||
document.getElementById("2_16").classList.add("filled");
|
||||
document.getElementById("2_17").classList.add("filled");
|
||||
document.getElementById("2_18").classList.add("filled");
|
||||
document.getElementById("3_16").classList.add("filled");
|
||||
document.getElementById("3_17").classList.add("filled");
|
||||
document.getElementById("3_18").classList.add("filled");
|
||||
document.getElementById("4_16").classList.add("filled");
|
||||
document.getElementById("4_17").classList.add("filled");
|
||||
document.getElementById("4_18").classList.add("filled");
|
||||
document.getElementById("0_14").classList.add("no_border");
|
||||
document.getElementById("0_15").classList.add("no_border");
|
||||
document.getElementById("0_16").classList.add("no_border");
|
||||
document.getElementById("0_17").classList.add("no_border");
|
||||
document.getElementById("0_18").classList.add("no_border");
|
||||
document.getElementById("0_19").classList.add("no_border");
|
||||
document.getElementById("0_20").classList.add("no_border");
|
||||
document.getElementById("1_14").classList.add("no_border");
|
||||
document.getElementById("1_15").classList.add("no_border");
|
||||
document.getElementById("1_16").classList.add("no_border");
|
||||
document.getElementById("1_17").classList.add("no_border");
|
||||
document.getElementById("1_18").classList.add("no_border");
|
||||
document.getElementById("1_19").classList.add("no_border");
|
||||
document.getElementById("1_20").classList.add("no_border");
|
||||
document.getElementById("2_14").classList.add("no_border");
|
||||
document.getElementById("2_15").classList.add("no_border");
|
||||
document.getElementById("2_16").classList.add("no_border");
|
||||
document.getElementById("2_17").classList.add("no_border");
|
||||
document.getElementById("2_18").classList.add("no_border");
|
||||
document.getElementById("2_19").classList.add("no_border");
|
||||
document.getElementById("2_20").classList.add("no_border");
|
||||
document.getElementById("3_14").classList.add("no_border");
|
||||
document.getElementById("3_15").classList.add("no_border");
|
||||
document.getElementById("3_16").classList.add("no_border");
|
||||
document.getElementById("3_17").classList.add("no_border");
|
||||
document.getElementById("3_18").classList.add("no_border");
|
||||
document.getElementById("3_19").classList.add("no_border");
|
||||
document.getElementById("3_20").classList.add("no_border");
|
||||
document.getElementById("4_14").classList.add("no_border");
|
||||
document.getElementById("4_15").classList.add("no_border");
|
||||
document.getElementById("4_16").classList.add("no_border");
|
||||
document.getElementById("4_17").classList.add("no_border");
|
||||
document.getElementById("4_18").classList.add("no_border");
|
||||
document.getElementById("4_19").classList.add("no_border");
|
||||
document.getElementById("4_20").classList.add("no_border");
|
||||
document.getElementById("5_14").classList.add("no_border");
|
||||
document.getElementById("5_15").classList.add("no_border");
|
||||
document.getElementById("5_16").classList.add("no_border");
|
||||
document.getElementById("5_17").classList.add("no_border");
|
||||
document.getElementById("5_18").classList.add("no_border");
|
||||
document.getElementById("5_19").classList.add("no_border");
|
||||
document.getElementById("5_20").classList.add("no_border");
|
||||
document.getElementById("6_14").classList.add("no_border");
|
||||
document.getElementById("6_15").classList.add("no_border");
|
||||
document.getElementById("6_16").classList.add("no_border");
|
||||
document.getElementById("6_17").classList.add("no_border");
|
||||
document.getElementById("6_18").classList.add("no_border");
|
||||
document.getElementById("6_19").classList.add("no_border");
|
||||
document.getElementById("6_20").classList.add("no_border");
|
||||
document.getElementById("14_0").classList.add("filled");
|
||||
document.getElementById("14_6").classList.add("filled");
|
||||
document.getElementById("15_0").classList.add("filled");
|
||||
document.getElementById("15_6").classList.add("filled");
|
||||
document.getElementById("16_0").classList.add("filled");
|
||||
document.getElementById("16_6").classList.add("filled");
|
||||
document.getElementById("17_0").classList.add("filled");
|
||||
document.getElementById("17_6").classList.add("filled");
|
||||
document.getElementById("18_0").classList.add("filled");
|
||||
document.getElementById("18_6").classList.add("filled");
|
||||
document.getElementById("19_0").classList.add("filled");
|
||||
document.getElementById("19_6").classList.add("filled");
|
||||
document.getElementById("20_0").classList.add("filled");
|
||||
document.getElementById("20_6").classList.add("filled");
|
||||
document.getElementById("14_1").classList.add("filled");
|
||||
document.getElementById("20_1").classList.add("filled");
|
||||
document.getElementById("14_2").classList.add("filled");
|
||||
document.getElementById("20_2").classList.add("filled");
|
||||
document.getElementById("14_3").classList.add("filled");
|
||||
document.getElementById("20_3").classList.add("filled");
|
||||
document.getElementById("14_4").classList.add("filled");
|
||||
document.getElementById("20_4").classList.add("filled");
|
||||
document.getElementById("14_5").classList.add("filled");
|
||||
document.getElementById("20_5").classList.add("filled");
|
||||
document.getElementById("16_2").classList.add("filled");
|
||||
document.getElementById("16_3").classList.add("filled");
|
||||
document.getElementById("16_4").classList.add("filled");
|
||||
document.getElementById("17_2").classList.add("filled");
|
||||
document.getElementById("17_3").classList.add("filled");
|
||||
document.getElementById("17_4").classList.add("filled");
|
||||
document.getElementById("18_2").classList.add("filled");
|
||||
document.getElementById("18_3").classList.add("filled");
|
||||
document.getElementById("18_4").classList.add("filled");
|
||||
document.getElementById("14_0").classList.add("no_border");
|
||||
document.getElementById("14_1").classList.add("no_border");
|
||||
document.getElementById("14_2").classList.add("no_border");
|
||||
document.getElementById("14_3").classList.add("no_border");
|
||||
document.getElementById("14_4").classList.add("no_border");
|
||||
document.getElementById("14_5").classList.add("no_border");
|
||||
document.getElementById("14_6").classList.add("no_border");
|
||||
document.getElementById("15_0").classList.add("no_border");
|
||||
document.getElementById("15_1").classList.add("no_border");
|
||||
document.getElementById("15_2").classList.add("no_border");
|
||||
document.getElementById("15_3").classList.add("no_border");
|
||||
document.getElementById("15_4").classList.add("no_border");
|
||||
document.getElementById("15_5").classList.add("no_border");
|
||||
document.getElementById("15_6").classList.add("no_border");
|
||||
document.getElementById("16_0").classList.add("no_border");
|
||||
document.getElementById("16_1").classList.add("no_border");
|
||||
document.getElementById("16_2").classList.add("no_border");
|
||||
document.getElementById("16_3").classList.add("no_border");
|
||||
document.getElementById("16_4").classList.add("no_border");
|
||||
document.getElementById("16_5").classList.add("no_border");
|
||||
document.getElementById("16_6").classList.add("no_border");
|
||||
document.getElementById("17_0").classList.add("no_border");
|
||||
document.getElementById("17_1").classList.add("no_border");
|
||||
document.getElementById("17_2").classList.add("no_border");
|
||||
document.getElementById("17_3").classList.add("no_border");
|
||||
document.getElementById("17_4").classList.add("no_border");
|
||||
document.getElementById("17_5").classList.add("no_border");
|
||||
document.getElementById("17_6").classList.add("no_border");
|
||||
document.getElementById("18_0").classList.add("no_border");
|
||||
document.getElementById("18_1").classList.add("no_border");
|
||||
document.getElementById("18_2").classList.add("no_border");
|
||||
document.getElementById("18_3").classList.add("no_border");
|
||||
document.getElementById("18_4").classList.add("no_border");
|
||||
document.getElementById("18_5").classList.add("no_border");
|
||||
document.getElementById("18_6").classList.add("no_border");
|
||||
document.getElementById("19_0").classList.add("no_border");
|
||||
document.getElementById("19_1").classList.add("no_border");
|
||||
document.getElementById("19_2").classList.add("no_border");
|
||||
document.getElementById("19_3").classList.add("no_border");
|
||||
document.getElementById("19_4").classList.add("no_border");
|
||||
document.getElementById("19_5").classList.add("no_border");
|
||||
document.getElementById("19_6").classList.add("no_border");
|
||||
document.getElementById("20_0").classList.add("no_border");
|
||||
document.getElementById("20_1").classList.add("no_border");
|
||||
document.getElementById("20_2").classList.add("no_border");
|
||||
document.getElementById("20_3").classList.add("no_border");
|
||||
document.getElementById("20_4").classList.add("no_border");
|
||||
document.getElementById("20_5").classList.add("no_border");
|
||||
document.getElementById("20_6").classList.add("no_border");
|
||||
document.getElementById("0_4").classList.add("col_block_divider");
|
||||
document.getElementById("0_9").classList.add("col_block_divider");
|
||||
document.getElementById("0_14").classList.add("col_block_divider");
|
||||
document.getElementById("0_19").classList.add("col_block_divider");
|
||||
document.getElementById("1_4").classList.add("col_block_divider");
|
||||
document.getElementById("1_9").classList.add("col_block_divider");
|
||||
document.getElementById("1_14").classList.add("col_block_divider");
|
||||
document.getElementById("1_19").classList.add("col_block_divider");
|
||||
document.getElementById("2_4").classList.add("col_block_divider");
|
||||
document.getElementById("2_9").classList.add("col_block_divider");
|
||||
document.getElementById("2_14").classList.add("col_block_divider");
|
||||
document.getElementById("2_19").classList.add("col_block_divider");
|
||||
document.getElementById("3_4").classList.add("col_block_divider");
|
||||
document.getElementById("3_9").classList.add("col_block_divider");
|
||||
document.getElementById("3_14").classList.add("col_block_divider");
|
||||
document.getElementById("3_19").classList.add("col_block_divider");
|
||||
document.getElementById("4_4").classList.add("col_block_divider");
|
||||
document.getElementById("4_9").classList.add("col_block_divider");
|
||||
document.getElementById("4_14").classList.add("col_block_divider");
|
||||
document.getElementById("4_19").classList.add("col_block_divider");
|
||||
document.getElementById("5_4").classList.add("col_block_divider");
|
||||
document.getElementById("5_9").classList.add("col_block_divider");
|
||||
document.getElementById("5_14").classList.add("col_block_divider");
|
||||
document.getElementById("5_19").classList.add("col_block_divider");
|
||||
document.getElementById("6_4").classList.add("col_block_divider");
|
||||
document.getElementById("6_9").classList.add("col_block_divider");
|
||||
document.getElementById("6_14").classList.add("col_block_divider");
|
||||
document.getElementById("6_19").classList.add("col_block_divider");
|
||||
document.getElementById("7_4").classList.add("col_block_divider");
|
||||
document.getElementById("7_9").classList.add("col_block_divider");
|
||||
document.getElementById("7_14").classList.add("col_block_divider");
|
||||
document.getElementById("7_19").classList.add("col_block_divider");
|
||||
document.getElementById("8_4").classList.add("col_block_divider");
|
||||
document.getElementById("8_9").classList.add("col_block_divider");
|
||||
document.getElementById("8_14").classList.add("col_block_divider");
|
||||
document.getElementById("8_19").classList.add("col_block_divider");
|
||||
document.getElementById("9_4").classList.add("col_block_divider");
|
||||
document.getElementById("9_9").classList.add("col_block_divider");
|
||||
document.getElementById("9_14").classList.add("col_block_divider");
|
||||
document.getElementById("9_19").classList.add("col_block_divider");
|
||||
document.getElementById("10_4").classList.add("col_block_divider");
|
||||
document.getElementById("10_9").classList.add("col_block_divider");
|
||||
document.getElementById("10_14").classList.add("col_block_divider");
|
||||
document.getElementById("10_19").classList.add("col_block_divider");
|
||||
document.getElementById("11_4").classList.add("col_block_divider");
|
||||
document.getElementById("11_9").classList.add("col_block_divider");
|
||||
document.getElementById("11_14").classList.add("col_block_divider");
|
||||
document.getElementById("11_19").classList.add("col_block_divider");
|
||||
document.getElementById("12_4").classList.add("col_block_divider");
|
||||
document.getElementById("12_9").classList.add("col_block_divider");
|
||||
document.getElementById("12_14").classList.add("col_block_divider");
|
||||
document.getElementById("12_19").classList.add("col_block_divider");
|
||||
document.getElementById("13_4").classList.add("col_block_divider");
|
||||
document.getElementById("13_9").classList.add("col_block_divider");
|
||||
document.getElementById("13_14").classList.add("col_block_divider");
|
||||
document.getElementById("13_19").classList.add("col_block_divider");
|
||||
document.getElementById("14_4").classList.add("col_block_divider");
|
||||
document.getElementById("14_9").classList.add("col_block_divider");
|
||||
document.getElementById("14_14").classList.add("col_block_divider");
|
||||
document.getElementById("14_19").classList.add("col_block_divider");
|
||||
document.getElementById("15_4").classList.add("col_block_divider");
|
||||
document.getElementById("15_9").classList.add("col_block_divider");
|
||||
document.getElementById("15_14").classList.add("col_block_divider");
|
||||
document.getElementById("15_19").classList.add("col_block_divider");
|
||||
document.getElementById("16_4").classList.add("col_block_divider");
|
||||
document.getElementById("16_9").classList.add("col_block_divider");
|
||||
document.getElementById("16_14").classList.add("col_block_divider");
|
||||
document.getElementById("16_19").classList.add("col_block_divider");
|
||||
document.getElementById("17_4").classList.add("col_block_divider");
|
||||
document.getElementById("17_9").classList.add("col_block_divider");
|
||||
document.getElementById("17_14").classList.add("col_block_divider");
|
||||
document.getElementById("17_19").classList.add("col_block_divider");
|
||||
document.getElementById("18_4").classList.add("col_block_divider");
|
||||
document.getElementById("18_9").classList.add("col_block_divider");
|
||||
document.getElementById("18_14").classList.add("col_block_divider");
|
||||
document.getElementById("18_19").classList.add("col_block_divider");
|
||||
document.getElementById("19_4").classList.add("col_block_divider");
|
||||
document.getElementById("19_9").classList.add("col_block_divider");
|
||||
document.getElementById("19_14").classList.add("col_block_divider");
|
||||
document.getElementById("19_19").classList.add("col_block_divider");
|
||||
document.getElementById("20_4").classList.add("col_block_divider");
|
||||
document.getElementById("20_9").classList.add("col_block_divider");
|
||||
document.getElementById("20_14").classList.add("col_block_divider");
|
||||
document.getElementById("20_19").classList.add("col_block_divider");
|
||||
document.getElementById("4_0").classList.add("row_block_divider");
|
||||
document.getElementById("4_1").classList.add("row_block_divider");
|
||||
document.getElementById("4_2").classList.add("row_block_divider");
|
||||
document.getElementById("4_3").classList.add("row_block_divider");
|
||||
document.getElementById("4_4").classList.add("row_block_divider");
|
||||
document.getElementById("4_5").classList.add("row_block_divider");
|
||||
document.getElementById("4_6").classList.add("row_block_divider");
|
||||
document.getElementById("4_7").classList.add("row_block_divider");
|
||||
document.getElementById("4_8").classList.add("row_block_divider");
|
||||
document.getElementById("4_9").classList.add("row_block_divider");
|
||||
document.getElementById("4_10").classList.add("row_block_divider");
|
||||
document.getElementById("4_11").classList.add("row_block_divider");
|
||||
document.getElementById("4_12").classList.add("row_block_divider");
|
||||
document.getElementById("4_13").classList.add("row_block_divider");
|
||||
document.getElementById("4_14").classList.add("row_block_divider");
|
||||
document.getElementById("4_15").classList.add("row_block_divider");
|
||||
document.getElementById("4_16").classList.add("row_block_divider");
|
||||
document.getElementById("4_17").classList.add("row_block_divider");
|
||||
document.getElementById("4_18").classList.add("row_block_divider");
|
||||
document.getElementById("4_19").classList.add("row_block_divider");
|
||||
document.getElementById("4_20").classList.add("row_block_divider");
|
||||
document.getElementById("9_0").classList.add("row_block_divider");
|
||||
document.getElementById("9_1").classList.add("row_block_divider");
|
||||
document.getElementById("9_2").classList.add("row_block_divider");
|
||||
document.getElementById("9_3").classList.add("row_block_divider");
|
||||
document.getElementById("9_4").classList.add("row_block_divider");
|
||||
document.getElementById("9_5").classList.add("row_block_divider");
|
||||
document.getElementById("9_6").classList.add("row_block_divider");
|
||||
document.getElementById("9_7").classList.add("row_block_divider");
|
||||
document.getElementById("9_8").classList.add("row_block_divider");
|
||||
document.getElementById("9_9").classList.add("row_block_divider");
|
||||
document.getElementById("9_10").classList.add("row_block_divider");
|
||||
document.getElementById("9_11").classList.add("row_block_divider");
|
||||
document.getElementById("9_12").classList.add("row_block_divider");
|
||||
document.getElementById("9_13").classList.add("row_block_divider");
|
||||
document.getElementById("9_14").classList.add("row_block_divider");
|
||||
document.getElementById("9_15").classList.add("row_block_divider");
|
||||
document.getElementById("9_16").classList.add("row_block_divider");
|
||||
document.getElementById("9_17").classList.add("row_block_divider");
|
||||
document.getElementById("9_18").classList.add("row_block_divider");
|
||||
document.getElementById("9_19").classList.add("row_block_divider");
|
||||
document.getElementById("9_20").classList.add("row_block_divider");
|
||||
document.getElementById("14_0").classList.add("row_block_divider");
|
||||
document.getElementById("14_1").classList.add("row_block_divider");
|
||||
document.getElementById("14_2").classList.add("row_block_divider");
|
||||
document.getElementById("14_3").classList.add("row_block_divider");
|
||||
document.getElementById("14_4").classList.add("row_block_divider");
|
||||
document.getElementById("14_5").classList.add("row_block_divider");
|
||||
document.getElementById("14_6").classList.add("row_block_divider");
|
||||
document.getElementById("14_7").classList.add("row_block_divider");
|
||||
document.getElementById("14_8").classList.add("row_block_divider");
|
||||
document.getElementById("14_9").classList.add("row_block_divider");
|
||||
document.getElementById("14_10").classList.add("row_block_divider");
|
||||
document.getElementById("14_11").classList.add("row_block_divider");
|
||||
document.getElementById("14_12").classList.add("row_block_divider");
|
||||
document.getElementById("14_13").classList.add("row_block_divider");
|
||||
document.getElementById("14_14").classList.add("row_block_divider");
|
||||
document.getElementById("14_15").classList.add("row_block_divider");
|
||||
document.getElementById("14_16").classList.add("row_block_divider");
|
||||
document.getElementById("14_17").classList.add("row_block_divider");
|
||||
document.getElementById("14_18").classList.add("row_block_divider");
|
||||
document.getElementById("14_19").classList.add("row_block_divider");
|
||||
document.getElementById("14_20").classList.add("row_block_divider");
|
||||
document.getElementById("19_0").classList.add("row_block_divider");
|
||||
document.getElementById("19_1").classList.add("row_block_divider");
|
||||
document.getElementById("19_2").classList.add("row_block_divider");
|
||||
document.getElementById("19_3").classList.add("row_block_divider");
|
||||
document.getElementById("19_4").classList.add("row_block_divider");
|
||||
document.getElementById("19_5").classList.add("row_block_divider");
|
||||
document.getElementById("19_6").classList.add("row_block_divider");
|
||||
document.getElementById("19_7").classList.add("row_block_divider");
|
||||
document.getElementById("19_8").classList.add("row_block_divider");
|
||||
document.getElementById("19_9").classList.add("row_block_divider");
|
||||
document.getElementById("19_10").classList.add("row_block_divider");
|
||||
document.getElementById("19_11").classList.add("row_block_divider");
|
||||
document.getElementById("19_12").classList.add("row_block_divider");
|
||||
document.getElementById("19_13").classList.add("row_block_divider");
|
||||
document.getElementById("19_14").classList.add("row_block_divider");
|
||||
document.getElementById("19_15").classList.add("row_block_divider");
|
||||
document.getElementById("19_16").classList.add("row_block_divider");
|
||||
document.getElementById("19_17").classList.add("row_block_divider");
|
||||
document.getElementById("19_18").classList.add("row_block_divider");
|
||||
document.getElementById("19_19").classList.add("row_block_divider");
|
||||
document.getElementById("19_20").classList.add("row_block_divider");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,716 @@
|
||||
|
||||
<html>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin-top: 5em;
|
||||
text-align: center;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
.title {
|
||||
margin-top: 0.5em;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 1em
|
||||
}
|
||||
table {
|
||||
margin-top: 1em;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px dotted #c8c8c8;
|
||||
width: 11px;
|
||||
height: 12px;
|
||||
}
|
||||
.qr_table {
|
||||
margin-top: 5em;
|
||||
margin-right: 4em;
|
||||
float: right;
|
||||
}
|
||||
.filled {
|
||||
background-color: black;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.col_name, .row_name {
|
||||
text-align: center;
|
||||
font-size: 0.7em;
|
||||
color: #aaa;
|
||||
}
|
||||
.col_name {
|
||||
border-bottom: 1px solid #bbb;
|
||||
height: 2em;
|
||||
}
|
||||
.row_name {
|
||||
border-right: 1px solid #bbb;
|
||||
width: 2em;
|
||||
}
|
||||
.col_block_divider {
|
||||
border-right: 1px solid #bbb;
|
||||
}
|
||||
.row_block_divider {
|
||||
border-bottom: 1px solid #bbb;
|
||||
}
|
||||
.no_border {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.word_list_table {
|
||||
margin-left: 10em;
|
||||
|
||||
float: left;
|
||||
}
|
||||
.word_list {
|
||||
text-align: right;
|
||||
padding-top: 2em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.word_row {
|
||||
height: 2.5em;
|
||||
}
|
||||
.word_num {
|
||||
width: 1em;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
}
|
||||
.word_blank {
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
|
||||
<table align="center" class="word_list_table">
|
||||
<tr>
|
||||
<td class="word_list">
|
||||
<div class="word_row">
|
||||
<span class="word_num">1: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">2: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">3: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">4: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">5: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">6: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">7: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">8: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">9: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">10: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">11: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">12: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
</td></tr></table><table align="center" class="qr_table"><tr rowspan="5"><td class="col_block_divider row_block_divider"></td><td colspan="5" class="col_name col_block_divider">1</td><td colspan="5" class="col_name col_block_divider">2</td><td colspan="5" class="col_name col_block_divider">3</td><td colspan="5" class="col_name col_block_divider">4</td><td colspan="5" class="col_name col_block_divider">5</td></tr><tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">A</td><td id="0_0"></td><td id="0_1"></td><td id="0_2"></td><td id="0_3"></td><td id="0_4"></td><td id="0_5"></td><td id="0_6"></td><td id="0_7"></td><td id="0_8"></td><td id="0_9"></td><td id="0_10"></td><td id="0_11"></td><td id="0_12"></td><td id="0_13"></td><td id="0_14"></td><td id="0_15"></td><td id="0_16"></td><td id="0_17"></td><td id="0_18"></td><td id="0_19"></td><td id="0_20"></td><td id="0_21"></td><td id="0_22"></td><td id="0_23"></td><td id="0_24"></td></tr>
|
||||
<tr>
|
||||
<td id="1_0"></td><td id="1_1"></td><td id="1_2"></td><td id="1_3"></td><td id="1_4"></td><td id="1_5"></td><td id="1_6"></td><td id="1_7"></td><td id="1_8"></td><td id="1_9"></td><td id="1_10"></td><td id="1_11"></td><td id="1_12"></td><td id="1_13"></td><td id="1_14"></td><td id="1_15"></td><td id="1_16"></td><td id="1_17"></td><td id="1_18"></td><td id="1_19"></td><td id="1_20"></td><td id="1_21"></td><td id="1_22"></td><td id="1_23"></td><td id="1_24"></td></tr>
|
||||
<tr>
|
||||
<td id="2_0"></td><td id="2_1"></td><td id="2_2"></td><td id="2_3"></td><td id="2_4"></td><td id="2_5"></td><td id="2_6"></td><td id="2_7"></td><td id="2_8"></td><td id="2_9"></td><td id="2_10"></td><td id="2_11"></td><td id="2_12"></td><td id="2_13"></td><td id="2_14"></td><td id="2_15"></td><td id="2_16"></td><td id="2_17"></td><td id="2_18"></td><td id="2_19"></td><td id="2_20"></td><td id="2_21"></td><td id="2_22"></td><td id="2_23"></td><td id="2_24"></td></tr>
|
||||
<tr>
|
||||
<td id="3_0"></td><td id="3_1"></td><td id="3_2"></td><td id="3_3"></td><td id="3_4"></td><td id="3_5"></td><td id="3_6"></td><td id="3_7"></td><td id="3_8"></td><td id="3_9"></td><td id="3_10"></td><td id="3_11"></td><td id="3_12"></td><td id="3_13"></td><td id="3_14"></td><td id="3_15"></td><td id="3_16"></td><td id="3_17"></td><td id="3_18"></td><td id="3_19"></td><td id="3_20"></td><td id="3_21"></td><td id="3_22"></td><td id="3_23"></td><td id="3_24"></td></tr>
|
||||
<tr>
|
||||
<td id="4_0"></td><td id="4_1"></td><td id="4_2"></td><td id="4_3"></td><td id="4_4"></td><td id="4_5"></td><td id="4_6"></td><td id="4_7"></td><td id="4_8"></td><td id="4_9"></td><td id="4_10"></td><td id="4_11"></td><td id="4_12"></td><td id="4_13"></td><td id="4_14"></td><td id="4_15"></td><td id="4_16"></td><td id="4_17"></td><td id="4_18"></td><td id="4_19"></td><td id="4_20"></td><td id="4_21"></td><td id="4_22"></td><td id="4_23"></td><td id="4_24"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">B</td><td id="5_0"></td><td id="5_1"></td><td id="5_2"></td><td id="5_3"></td><td id="5_4"></td><td id="5_5"></td><td id="5_6"></td><td id="5_7"></td><td id="5_8"></td><td id="5_9"></td><td id="5_10"></td><td id="5_11"></td><td id="5_12"></td><td id="5_13"></td><td id="5_14"></td><td id="5_15"></td><td id="5_16"></td><td id="5_17"></td><td id="5_18"></td><td id="5_19"></td><td id="5_20"></td><td id="5_21"></td><td id="5_22"></td><td id="5_23"></td><td id="5_24"></td></tr>
|
||||
<tr>
|
||||
<td id="6_0"></td><td id="6_1"></td><td id="6_2"></td><td id="6_3"></td><td id="6_4"></td><td id="6_5"></td><td id="6_6"></td><td id="6_7"></td><td id="6_8"></td><td id="6_9"></td><td id="6_10"></td><td id="6_11"></td><td id="6_12"></td><td id="6_13"></td><td id="6_14"></td><td id="6_15"></td><td id="6_16"></td><td id="6_17"></td><td id="6_18"></td><td id="6_19"></td><td id="6_20"></td><td id="6_21"></td><td id="6_22"></td><td id="6_23"></td><td id="6_24"></td></tr>
|
||||
<tr>
|
||||
<td id="7_0"></td><td id="7_1"></td><td id="7_2"></td><td id="7_3"></td><td id="7_4"></td><td id="7_5"></td><td id="7_6"></td><td id="7_7"></td><td id="7_8"></td><td id="7_9"></td><td id="7_10"></td><td id="7_11"></td><td id="7_12"></td><td id="7_13"></td><td id="7_14"></td><td id="7_15"></td><td id="7_16"></td><td id="7_17"></td><td id="7_18"></td><td id="7_19"></td><td id="7_20"></td><td id="7_21"></td><td id="7_22"></td><td id="7_23"></td><td id="7_24"></td></tr>
|
||||
<tr>
|
||||
<td id="8_0"></td><td id="8_1"></td><td id="8_2"></td><td id="8_3"></td><td id="8_4"></td><td id="8_5"></td><td id="8_6"></td><td id="8_7"></td><td id="8_8"></td><td id="8_9"></td><td id="8_10"></td><td id="8_11"></td><td id="8_12"></td><td id="8_13"></td><td id="8_14"></td><td id="8_15"></td><td id="8_16"></td><td id="8_17"></td><td id="8_18"></td><td id="8_19"></td><td id="8_20"></td><td id="8_21"></td><td id="8_22"></td><td id="8_23"></td><td id="8_24"></td></tr>
|
||||
<tr>
|
||||
<td id="9_0"></td><td id="9_1"></td><td id="9_2"></td><td id="9_3"></td><td id="9_4"></td><td id="9_5"></td><td id="9_6"></td><td id="9_7"></td><td id="9_8"></td><td id="9_9"></td><td id="9_10"></td><td id="9_11"></td><td id="9_12"></td><td id="9_13"></td><td id="9_14"></td><td id="9_15"></td><td id="9_16"></td><td id="9_17"></td><td id="9_18"></td><td id="9_19"></td><td id="9_20"></td><td id="9_21"></td><td id="9_22"></td><td id="9_23"></td><td id="9_24"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">C</td><td id="10_0"></td><td id="10_1"></td><td id="10_2"></td><td id="10_3"></td><td id="10_4"></td><td id="10_5"></td><td id="10_6"></td><td id="10_7"></td><td id="10_8"></td><td id="10_9"></td><td id="10_10"></td><td id="10_11"></td><td id="10_12"></td><td id="10_13"></td><td id="10_14"></td><td id="10_15"></td><td id="10_16"></td><td id="10_17"></td><td id="10_18"></td><td id="10_19"></td><td id="10_20"></td><td id="10_21"></td><td id="10_22"></td><td id="10_23"></td><td id="10_24"></td></tr>
|
||||
<tr>
|
||||
<td id="11_0"></td><td id="11_1"></td><td id="11_2"></td><td id="11_3"></td><td id="11_4"></td><td id="11_5"></td><td id="11_6"></td><td id="11_7"></td><td id="11_8"></td><td id="11_9"></td><td id="11_10"></td><td id="11_11"></td><td id="11_12"></td><td id="11_13"></td><td id="11_14"></td><td id="11_15"></td><td id="11_16"></td><td id="11_17"></td><td id="11_18"></td><td id="11_19"></td><td id="11_20"></td><td id="11_21"></td><td id="11_22"></td><td id="11_23"></td><td id="11_24"></td></tr>
|
||||
<tr>
|
||||
<td id="12_0"></td><td id="12_1"></td><td id="12_2"></td><td id="12_3"></td><td id="12_4"></td><td id="12_5"></td><td id="12_6"></td><td id="12_7"></td><td id="12_8"></td><td id="12_9"></td><td id="12_10"></td><td id="12_11"></td><td id="12_12"></td><td id="12_13"></td><td id="12_14"></td><td id="12_15"></td><td id="12_16"></td><td id="12_17"></td><td id="12_18"></td><td id="12_19"></td><td id="12_20"></td><td id="12_21"></td><td id="12_22"></td><td id="12_23"></td><td id="12_24"></td></tr>
|
||||
<tr>
|
||||
<td id="13_0"></td><td id="13_1"></td><td id="13_2"></td><td id="13_3"></td><td id="13_4"></td><td id="13_5"></td><td id="13_6"></td><td id="13_7"></td><td id="13_8"></td><td id="13_9"></td><td id="13_10"></td><td id="13_11"></td><td id="13_12"></td><td id="13_13"></td><td id="13_14"></td><td id="13_15"></td><td id="13_16"></td><td id="13_17"></td><td id="13_18"></td><td id="13_19"></td><td id="13_20"></td><td id="13_21"></td><td id="13_22"></td><td id="13_23"></td><td id="13_24"></td></tr>
|
||||
<tr>
|
||||
<td id="14_0"></td><td id="14_1"></td><td id="14_2"></td><td id="14_3"></td><td id="14_4"></td><td id="14_5"></td><td id="14_6"></td><td id="14_7"></td><td id="14_8"></td><td id="14_9"></td><td id="14_10"></td><td id="14_11"></td><td id="14_12"></td><td id="14_13"></td><td id="14_14"></td><td id="14_15"></td><td id="14_16"></td><td id="14_17"></td><td id="14_18"></td><td id="14_19"></td><td id="14_20"></td><td id="14_21"></td><td id="14_22"></td><td id="14_23"></td><td id="14_24"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">D</td><td id="15_0"></td><td id="15_1"></td><td id="15_2"></td><td id="15_3"></td><td id="15_4"></td><td id="15_5"></td><td id="15_6"></td><td id="15_7"></td><td id="15_8"></td><td id="15_9"></td><td id="15_10"></td><td id="15_11"></td><td id="15_12"></td><td id="15_13"></td><td id="15_14"></td><td id="15_15"></td><td id="15_16"></td><td id="15_17"></td><td id="15_18"></td><td id="15_19"></td><td id="15_20"></td><td id="15_21"></td><td id="15_22"></td><td id="15_23"></td><td id="15_24"></td></tr>
|
||||
<tr>
|
||||
<td id="16_0"></td><td id="16_1"></td><td id="16_2"></td><td id="16_3"></td><td id="16_4"></td><td id="16_5"></td><td id="16_6"></td><td id="16_7"></td><td id="16_8"></td><td id="16_9"></td><td id="16_10"></td><td id="16_11"></td><td id="16_12"></td><td id="16_13"></td><td id="16_14"></td><td id="16_15"></td><td id="16_16"></td><td id="16_17"></td><td id="16_18"></td><td id="16_19"></td><td id="16_20"></td><td id="16_21"></td><td id="16_22"></td><td id="16_23"></td><td id="16_24"></td></tr>
|
||||
<tr>
|
||||
<td id="17_0"></td><td id="17_1"></td><td id="17_2"></td><td id="17_3"></td><td id="17_4"></td><td id="17_5"></td><td id="17_6"></td><td id="17_7"></td><td id="17_8"></td><td id="17_9"></td><td id="17_10"></td><td id="17_11"></td><td id="17_12"></td><td id="17_13"></td><td id="17_14"></td><td id="17_15"></td><td id="17_16"></td><td id="17_17"></td><td id="17_18"></td><td id="17_19"></td><td id="17_20"></td><td id="17_21"></td><td id="17_22"></td><td id="17_23"></td><td id="17_24"></td></tr>
|
||||
<tr>
|
||||
<td id="18_0"></td><td id="18_1"></td><td id="18_2"></td><td id="18_3"></td><td id="18_4"></td><td id="18_5"></td><td id="18_6"></td><td id="18_7"></td><td id="18_8"></td><td id="18_9"></td><td id="18_10"></td><td id="18_11"></td><td id="18_12"></td><td id="18_13"></td><td id="18_14"></td><td id="18_15"></td><td id="18_16"></td><td id="18_17"></td><td id="18_18"></td><td id="18_19"></td><td id="18_20"></td><td id="18_21"></td><td id="18_22"></td><td id="18_23"></td><td id="18_24"></td></tr>
|
||||
<tr>
|
||||
<td id="19_0"></td><td id="19_1"></td><td id="19_2"></td><td id="19_3"></td><td id="19_4"></td><td id="19_5"></td><td id="19_6"></td><td id="19_7"></td><td id="19_8"></td><td id="19_9"></td><td id="19_10"></td><td id="19_11"></td><td id="19_12"></td><td id="19_13"></td><td id="19_14"></td><td id="19_15"></td><td id="19_16"></td><td id="19_17"></td><td id="19_18"></td><td id="19_19"></td><td id="19_20"></td><td id="19_21"></td><td id="19_22"></td><td id="19_23"></td><td id="19_24"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">E</td><td id="20_0"></td><td id="20_1"></td><td id="20_2"></td><td id="20_3"></td><td id="20_4"></td><td id="20_5"></td><td id="20_6"></td><td id="20_7"></td><td id="20_8"></td><td id="20_9"></td><td id="20_10"></td><td id="20_11"></td><td id="20_12"></td><td id="20_13"></td><td id="20_14"></td><td id="20_15"></td><td id="20_16"></td><td id="20_17"></td><td id="20_18"></td><td id="20_19"></td><td id="20_20"></td><td id="20_21"></td><td id="20_22"></td><td id="20_23"></td><td id="20_24"></td></tr>
|
||||
<tr>
|
||||
<td id="21_0"></td><td id="21_1"></td><td id="21_2"></td><td id="21_3"></td><td id="21_4"></td><td id="21_5"></td><td id="21_6"></td><td id="21_7"></td><td id="21_8"></td><td id="21_9"></td><td id="21_10"></td><td id="21_11"></td><td id="21_12"></td><td id="21_13"></td><td id="21_14"></td><td id="21_15"></td><td id="21_16"></td><td id="21_17"></td><td id="21_18"></td><td id="21_19"></td><td id="21_20"></td><td id="21_21"></td><td id="21_22"></td><td id="21_23"></td><td id="21_24"></td></tr>
|
||||
<tr>
|
||||
<td id="22_0"></td><td id="22_1"></td><td id="22_2"></td><td id="22_3"></td><td id="22_4"></td><td id="22_5"></td><td id="22_6"></td><td id="22_7"></td><td id="22_8"></td><td id="22_9"></td><td id="22_10"></td><td id="22_11"></td><td id="22_12"></td><td id="22_13"></td><td id="22_14"></td><td id="22_15"></td><td id="22_16"></td><td id="22_17"></td><td id="22_18"></td><td id="22_19"></td><td id="22_20"></td><td id="22_21"></td><td id="22_22"></td><td id="22_23"></td><td id="22_24"></td></tr>
|
||||
<tr>
|
||||
<td id="23_0"></td><td id="23_1"></td><td id="23_2"></td><td id="23_3"></td><td id="23_4"></td><td id="23_5"></td><td id="23_6"></td><td id="23_7"></td><td id="23_8"></td><td id="23_9"></td><td id="23_10"></td><td id="23_11"></td><td id="23_12"></td><td id="23_13"></td><td id="23_14"></td><td id="23_15"></td><td id="23_16"></td><td id="23_17"></td><td id="23_18"></td><td id="23_19"></td><td id="23_20"></td><td id="23_21"></td><td id="23_22"></td><td id="23_23"></td><td id="23_24"></td></tr>
|
||||
<tr>
|
||||
<td id="24_0"></td><td id="24_1"></td><td id="24_2"></td><td id="24_3"></td><td id="24_4"></td><td id="24_5"></td><td id="24_6"></td><td id="24_7"></td><td id="24_8"></td><td id="24_9"></td><td id="24_10"></td><td id="24_11"></td><td id="24_12"></td><td id="24_13"></td><td id="24_14"></td><td id="24_15"></td><td id="24_16"></td><td id="24_17"></td><td id="24_18"></td><td id="24_19"></td><td id="24_20"></td><td id="24_21"></td><td id="24_22"></td><td id="24_23"></td><td id="24_24"></td></tr>
|
||||
</table><script>document.getElementById("0_0").classList.add("filled");
|
||||
document.getElementById("0_6").classList.add("filled");
|
||||
document.getElementById("1_0").classList.add("filled");
|
||||
document.getElementById("1_6").classList.add("filled");
|
||||
document.getElementById("2_0").classList.add("filled");
|
||||
document.getElementById("2_6").classList.add("filled");
|
||||
document.getElementById("3_0").classList.add("filled");
|
||||
document.getElementById("3_6").classList.add("filled");
|
||||
document.getElementById("4_0").classList.add("filled");
|
||||
document.getElementById("4_6").classList.add("filled");
|
||||
document.getElementById("5_0").classList.add("filled");
|
||||
document.getElementById("5_6").classList.add("filled");
|
||||
document.getElementById("6_0").classList.add("filled");
|
||||
document.getElementById("6_6").classList.add("filled");
|
||||
document.getElementById("0_1").classList.add("filled");
|
||||
document.getElementById("6_1").classList.add("filled");
|
||||
document.getElementById("0_2").classList.add("filled");
|
||||
document.getElementById("6_2").classList.add("filled");
|
||||
document.getElementById("0_3").classList.add("filled");
|
||||
document.getElementById("6_3").classList.add("filled");
|
||||
document.getElementById("0_4").classList.add("filled");
|
||||
document.getElementById("6_4").classList.add("filled");
|
||||
document.getElementById("0_5").classList.add("filled");
|
||||
document.getElementById("6_5").classList.add("filled");
|
||||
document.getElementById("2_2").classList.add("filled");
|
||||
document.getElementById("2_3").classList.add("filled");
|
||||
document.getElementById("2_4").classList.add("filled");
|
||||
document.getElementById("3_2").classList.add("filled");
|
||||
document.getElementById("3_3").classList.add("filled");
|
||||
document.getElementById("3_4").classList.add("filled");
|
||||
document.getElementById("4_2").classList.add("filled");
|
||||
document.getElementById("4_3").classList.add("filled");
|
||||
document.getElementById("4_4").classList.add("filled");
|
||||
document.getElementById("0_0").classList.add("no_border");
|
||||
document.getElementById("0_1").classList.add("no_border");
|
||||
document.getElementById("0_2").classList.add("no_border");
|
||||
document.getElementById("0_3").classList.add("no_border");
|
||||
document.getElementById("0_4").classList.add("no_border");
|
||||
document.getElementById("0_5").classList.add("no_border");
|
||||
document.getElementById("0_6").classList.add("no_border");
|
||||
document.getElementById("1_0").classList.add("no_border");
|
||||
document.getElementById("1_1").classList.add("no_border");
|
||||
document.getElementById("1_2").classList.add("no_border");
|
||||
document.getElementById("1_3").classList.add("no_border");
|
||||
document.getElementById("1_4").classList.add("no_border");
|
||||
document.getElementById("1_5").classList.add("no_border");
|
||||
document.getElementById("1_6").classList.add("no_border");
|
||||
document.getElementById("2_0").classList.add("no_border");
|
||||
document.getElementById("2_1").classList.add("no_border");
|
||||
document.getElementById("2_2").classList.add("no_border");
|
||||
document.getElementById("2_3").classList.add("no_border");
|
||||
document.getElementById("2_4").classList.add("no_border");
|
||||
document.getElementById("2_5").classList.add("no_border");
|
||||
document.getElementById("2_6").classList.add("no_border");
|
||||
document.getElementById("3_0").classList.add("no_border");
|
||||
document.getElementById("3_1").classList.add("no_border");
|
||||
document.getElementById("3_2").classList.add("no_border");
|
||||
document.getElementById("3_3").classList.add("no_border");
|
||||
document.getElementById("3_4").classList.add("no_border");
|
||||
document.getElementById("3_5").classList.add("no_border");
|
||||
document.getElementById("3_6").classList.add("no_border");
|
||||
document.getElementById("4_0").classList.add("no_border");
|
||||
document.getElementById("4_1").classList.add("no_border");
|
||||
document.getElementById("4_2").classList.add("no_border");
|
||||
document.getElementById("4_3").classList.add("no_border");
|
||||
document.getElementById("4_4").classList.add("no_border");
|
||||
document.getElementById("4_5").classList.add("no_border");
|
||||
document.getElementById("4_6").classList.add("no_border");
|
||||
document.getElementById("5_0").classList.add("no_border");
|
||||
document.getElementById("5_1").classList.add("no_border");
|
||||
document.getElementById("5_2").classList.add("no_border");
|
||||
document.getElementById("5_3").classList.add("no_border");
|
||||
document.getElementById("5_4").classList.add("no_border");
|
||||
document.getElementById("5_5").classList.add("no_border");
|
||||
document.getElementById("5_6").classList.add("no_border");
|
||||
document.getElementById("6_0").classList.add("no_border");
|
||||
document.getElementById("6_1").classList.add("no_border");
|
||||
document.getElementById("6_2").classList.add("no_border");
|
||||
document.getElementById("6_3").classList.add("no_border");
|
||||
document.getElementById("6_4").classList.add("no_border");
|
||||
document.getElementById("6_5").classList.add("no_border");
|
||||
document.getElementById("6_6").classList.add("no_border");
|
||||
document.getElementById("0_18").classList.add("filled");
|
||||
document.getElementById("0_24").classList.add("filled");
|
||||
document.getElementById("1_18").classList.add("filled");
|
||||
document.getElementById("1_24").classList.add("filled");
|
||||
document.getElementById("2_18").classList.add("filled");
|
||||
document.getElementById("2_24").classList.add("filled");
|
||||
document.getElementById("3_18").classList.add("filled");
|
||||
document.getElementById("3_24").classList.add("filled");
|
||||
document.getElementById("4_18").classList.add("filled");
|
||||
document.getElementById("4_24").classList.add("filled");
|
||||
document.getElementById("5_18").classList.add("filled");
|
||||
document.getElementById("5_24").classList.add("filled");
|
||||
document.getElementById("6_18").classList.add("filled");
|
||||
document.getElementById("6_24").classList.add("filled");
|
||||
document.getElementById("0_19").classList.add("filled");
|
||||
document.getElementById("6_19").classList.add("filled");
|
||||
document.getElementById("0_20").classList.add("filled");
|
||||
document.getElementById("6_20").classList.add("filled");
|
||||
document.getElementById("0_21").classList.add("filled");
|
||||
document.getElementById("6_21").classList.add("filled");
|
||||
document.getElementById("0_22").classList.add("filled");
|
||||
document.getElementById("6_22").classList.add("filled");
|
||||
document.getElementById("0_23").classList.add("filled");
|
||||
document.getElementById("6_23").classList.add("filled");
|
||||
document.getElementById("2_20").classList.add("filled");
|
||||
document.getElementById("2_21").classList.add("filled");
|
||||
document.getElementById("2_22").classList.add("filled");
|
||||
document.getElementById("3_20").classList.add("filled");
|
||||
document.getElementById("3_21").classList.add("filled");
|
||||
document.getElementById("3_22").classList.add("filled");
|
||||
document.getElementById("4_20").classList.add("filled");
|
||||
document.getElementById("4_21").classList.add("filled");
|
||||
document.getElementById("4_22").classList.add("filled");
|
||||
document.getElementById("0_18").classList.add("no_border");
|
||||
document.getElementById("0_19").classList.add("no_border");
|
||||
document.getElementById("0_20").classList.add("no_border");
|
||||
document.getElementById("0_21").classList.add("no_border");
|
||||
document.getElementById("0_22").classList.add("no_border");
|
||||
document.getElementById("0_23").classList.add("no_border");
|
||||
document.getElementById("0_24").classList.add("no_border");
|
||||
document.getElementById("1_18").classList.add("no_border");
|
||||
document.getElementById("1_19").classList.add("no_border");
|
||||
document.getElementById("1_20").classList.add("no_border");
|
||||
document.getElementById("1_21").classList.add("no_border");
|
||||
document.getElementById("1_22").classList.add("no_border");
|
||||
document.getElementById("1_23").classList.add("no_border");
|
||||
document.getElementById("1_24").classList.add("no_border");
|
||||
document.getElementById("2_18").classList.add("no_border");
|
||||
document.getElementById("2_19").classList.add("no_border");
|
||||
document.getElementById("2_20").classList.add("no_border");
|
||||
document.getElementById("2_21").classList.add("no_border");
|
||||
document.getElementById("2_22").classList.add("no_border");
|
||||
document.getElementById("2_23").classList.add("no_border");
|
||||
document.getElementById("2_24").classList.add("no_border");
|
||||
document.getElementById("3_18").classList.add("no_border");
|
||||
document.getElementById("3_19").classList.add("no_border");
|
||||
document.getElementById("3_20").classList.add("no_border");
|
||||
document.getElementById("3_21").classList.add("no_border");
|
||||
document.getElementById("3_22").classList.add("no_border");
|
||||
document.getElementById("3_23").classList.add("no_border");
|
||||
document.getElementById("3_24").classList.add("no_border");
|
||||
document.getElementById("4_18").classList.add("no_border");
|
||||
document.getElementById("4_19").classList.add("no_border");
|
||||
document.getElementById("4_20").classList.add("no_border");
|
||||
document.getElementById("4_21").classList.add("no_border");
|
||||
document.getElementById("4_22").classList.add("no_border");
|
||||
document.getElementById("4_23").classList.add("no_border");
|
||||
document.getElementById("4_24").classList.add("no_border");
|
||||
document.getElementById("5_18").classList.add("no_border");
|
||||
document.getElementById("5_19").classList.add("no_border");
|
||||
document.getElementById("5_20").classList.add("no_border");
|
||||
document.getElementById("5_21").classList.add("no_border");
|
||||
document.getElementById("5_22").classList.add("no_border");
|
||||
document.getElementById("5_23").classList.add("no_border");
|
||||
document.getElementById("5_24").classList.add("no_border");
|
||||
document.getElementById("6_18").classList.add("no_border");
|
||||
document.getElementById("6_19").classList.add("no_border");
|
||||
document.getElementById("6_20").classList.add("no_border");
|
||||
document.getElementById("6_21").classList.add("no_border");
|
||||
document.getElementById("6_22").classList.add("no_border");
|
||||
document.getElementById("6_23").classList.add("no_border");
|
||||
document.getElementById("6_24").classList.add("no_border");
|
||||
document.getElementById("18_0").classList.add("filled");
|
||||
document.getElementById("18_6").classList.add("filled");
|
||||
document.getElementById("19_0").classList.add("filled");
|
||||
document.getElementById("19_6").classList.add("filled");
|
||||
document.getElementById("20_0").classList.add("filled");
|
||||
document.getElementById("20_6").classList.add("filled");
|
||||
document.getElementById("21_0").classList.add("filled");
|
||||
document.getElementById("21_6").classList.add("filled");
|
||||
document.getElementById("22_0").classList.add("filled");
|
||||
document.getElementById("22_6").classList.add("filled");
|
||||
document.getElementById("23_0").classList.add("filled");
|
||||
document.getElementById("23_6").classList.add("filled");
|
||||
document.getElementById("24_0").classList.add("filled");
|
||||
document.getElementById("24_6").classList.add("filled");
|
||||
document.getElementById("18_1").classList.add("filled");
|
||||
document.getElementById("24_1").classList.add("filled");
|
||||
document.getElementById("18_2").classList.add("filled");
|
||||
document.getElementById("24_2").classList.add("filled");
|
||||
document.getElementById("18_3").classList.add("filled");
|
||||
document.getElementById("24_3").classList.add("filled");
|
||||
document.getElementById("18_4").classList.add("filled");
|
||||
document.getElementById("24_4").classList.add("filled");
|
||||
document.getElementById("18_5").classList.add("filled");
|
||||
document.getElementById("24_5").classList.add("filled");
|
||||
document.getElementById("20_2").classList.add("filled");
|
||||
document.getElementById("20_3").classList.add("filled");
|
||||
document.getElementById("20_4").classList.add("filled");
|
||||
document.getElementById("21_2").classList.add("filled");
|
||||
document.getElementById("21_3").classList.add("filled");
|
||||
document.getElementById("21_4").classList.add("filled");
|
||||
document.getElementById("22_2").classList.add("filled");
|
||||
document.getElementById("22_3").classList.add("filled");
|
||||
document.getElementById("22_4").classList.add("filled");
|
||||
document.getElementById("18_0").classList.add("no_border");
|
||||
document.getElementById("18_1").classList.add("no_border");
|
||||
document.getElementById("18_2").classList.add("no_border");
|
||||
document.getElementById("18_3").classList.add("no_border");
|
||||
document.getElementById("18_4").classList.add("no_border");
|
||||
document.getElementById("18_5").classList.add("no_border");
|
||||
document.getElementById("18_6").classList.add("no_border");
|
||||
document.getElementById("19_0").classList.add("no_border");
|
||||
document.getElementById("19_1").classList.add("no_border");
|
||||
document.getElementById("19_2").classList.add("no_border");
|
||||
document.getElementById("19_3").classList.add("no_border");
|
||||
document.getElementById("19_4").classList.add("no_border");
|
||||
document.getElementById("19_5").classList.add("no_border");
|
||||
document.getElementById("19_6").classList.add("no_border");
|
||||
document.getElementById("20_0").classList.add("no_border");
|
||||
document.getElementById("20_1").classList.add("no_border");
|
||||
document.getElementById("20_2").classList.add("no_border");
|
||||
document.getElementById("20_3").classList.add("no_border");
|
||||
document.getElementById("20_4").classList.add("no_border");
|
||||
document.getElementById("20_5").classList.add("no_border");
|
||||
document.getElementById("20_6").classList.add("no_border");
|
||||
document.getElementById("21_0").classList.add("no_border");
|
||||
document.getElementById("21_1").classList.add("no_border");
|
||||
document.getElementById("21_2").classList.add("no_border");
|
||||
document.getElementById("21_3").classList.add("no_border");
|
||||
document.getElementById("21_4").classList.add("no_border");
|
||||
document.getElementById("21_5").classList.add("no_border");
|
||||
document.getElementById("21_6").classList.add("no_border");
|
||||
document.getElementById("22_0").classList.add("no_border");
|
||||
document.getElementById("22_1").classList.add("no_border");
|
||||
document.getElementById("22_2").classList.add("no_border");
|
||||
document.getElementById("22_3").classList.add("no_border");
|
||||
document.getElementById("22_4").classList.add("no_border");
|
||||
document.getElementById("22_5").classList.add("no_border");
|
||||
document.getElementById("22_6").classList.add("no_border");
|
||||
document.getElementById("23_0").classList.add("no_border");
|
||||
document.getElementById("23_1").classList.add("no_border");
|
||||
document.getElementById("23_2").classList.add("no_border");
|
||||
document.getElementById("23_3").classList.add("no_border");
|
||||
document.getElementById("23_4").classList.add("no_border");
|
||||
document.getElementById("23_5").classList.add("no_border");
|
||||
document.getElementById("23_6").classList.add("no_border");
|
||||
document.getElementById("24_0").classList.add("no_border");
|
||||
document.getElementById("24_1").classList.add("no_border");
|
||||
document.getElementById("24_2").classList.add("no_border");
|
||||
document.getElementById("24_3").classList.add("no_border");
|
||||
document.getElementById("24_4").classList.add("no_border");
|
||||
document.getElementById("24_5").classList.add("no_border");
|
||||
document.getElementById("24_6").classList.add("no_border");
|
||||
document.getElementById("18_18").classList.add("filled");
|
||||
document.getElementById("16_16").classList.add("filled");
|
||||
document.getElementById("16_20").classList.add("filled");
|
||||
document.getElementById("17_16").classList.add("filled");
|
||||
document.getElementById("17_20").classList.add("filled");
|
||||
document.getElementById("18_16").classList.add("filled");
|
||||
document.getElementById("18_20").classList.add("filled");
|
||||
document.getElementById("19_16").classList.add("filled");
|
||||
document.getElementById("19_20").classList.add("filled");
|
||||
document.getElementById("20_16").classList.add("filled");
|
||||
document.getElementById("20_20").classList.add("filled");
|
||||
document.getElementById("16_17").classList.add("filled");
|
||||
document.getElementById("20_17").classList.add("filled");
|
||||
document.getElementById("16_18").classList.add("filled");
|
||||
document.getElementById("20_18").classList.add("filled");
|
||||
document.getElementById("16_19").classList.add("filled");
|
||||
document.getElementById("20_19").classList.add("filled");
|
||||
document.getElementById("16_16").classList.add("no_border");
|
||||
document.getElementById("16_17").classList.add("no_border");
|
||||
document.getElementById("16_18").classList.add("no_border");
|
||||
document.getElementById("16_19").classList.add("no_border");
|
||||
document.getElementById("16_20").classList.add("no_border");
|
||||
document.getElementById("17_16").classList.add("no_border");
|
||||
document.getElementById("17_17").classList.add("no_border");
|
||||
document.getElementById("17_18").classList.add("no_border");
|
||||
document.getElementById("17_19").classList.add("no_border");
|
||||
document.getElementById("17_20").classList.add("no_border");
|
||||
document.getElementById("18_16").classList.add("no_border");
|
||||
document.getElementById("18_17").classList.add("no_border");
|
||||
document.getElementById("18_18").classList.add("no_border");
|
||||
document.getElementById("18_19").classList.add("no_border");
|
||||
document.getElementById("18_20").classList.add("no_border");
|
||||
document.getElementById("19_16").classList.add("no_border");
|
||||
document.getElementById("19_17").classList.add("no_border");
|
||||
document.getElementById("19_18").classList.add("no_border");
|
||||
document.getElementById("19_19").classList.add("no_border");
|
||||
document.getElementById("19_20").classList.add("no_border");
|
||||
document.getElementById("20_16").classList.add("no_border");
|
||||
document.getElementById("20_17").classList.add("no_border");
|
||||
document.getElementById("20_18").classList.add("no_border");
|
||||
document.getElementById("20_19").classList.add("no_border");
|
||||
document.getElementById("20_20").classList.add("no_border");
|
||||
document.getElementById("0_4").classList.add("col_block_divider");
|
||||
document.getElementById("0_9").classList.add("col_block_divider");
|
||||
document.getElementById("0_14").classList.add("col_block_divider");
|
||||
document.getElementById("0_19").classList.add("col_block_divider");
|
||||
document.getElementById("0_24").classList.add("col_block_divider");
|
||||
document.getElementById("1_4").classList.add("col_block_divider");
|
||||
document.getElementById("1_9").classList.add("col_block_divider");
|
||||
document.getElementById("1_14").classList.add("col_block_divider");
|
||||
document.getElementById("1_19").classList.add("col_block_divider");
|
||||
document.getElementById("1_24").classList.add("col_block_divider");
|
||||
document.getElementById("2_4").classList.add("col_block_divider");
|
||||
document.getElementById("2_9").classList.add("col_block_divider");
|
||||
document.getElementById("2_14").classList.add("col_block_divider");
|
||||
document.getElementById("2_19").classList.add("col_block_divider");
|
||||
document.getElementById("2_24").classList.add("col_block_divider");
|
||||
document.getElementById("3_4").classList.add("col_block_divider");
|
||||
document.getElementById("3_9").classList.add("col_block_divider");
|
||||
document.getElementById("3_14").classList.add("col_block_divider");
|
||||
document.getElementById("3_19").classList.add("col_block_divider");
|
||||
document.getElementById("3_24").classList.add("col_block_divider");
|
||||
document.getElementById("4_4").classList.add("col_block_divider");
|
||||
document.getElementById("4_9").classList.add("col_block_divider");
|
||||
document.getElementById("4_14").classList.add("col_block_divider");
|
||||
document.getElementById("4_19").classList.add("col_block_divider");
|
||||
document.getElementById("4_24").classList.add("col_block_divider");
|
||||
document.getElementById("5_4").classList.add("col_block_divider");
|
||||
document.getElementById("5_9").classList.add("col_block_divider");
|
||||
document.getElementById("5_14").classList.add("col_block_divider");
|
||||
document.getElementById("5_19").classList.add("col_block_divider");
|
||||
document.getElementById("5_24").classList.add("col_block_divider");
|
||||
document.getElementById("6_4").classList.add("col_block_divider");
|
||||
document.getElementById("6_9").classList.add("col_block_divider");
|
||||
document.getElementById("6_14").classList.add("col_block_divider");
|
||||
document.getElementById("6_19").classList.add("col_block_divider");
|
||||
document.getElementById("6_24").classList.add("col_block_divider");
|
||||
document.getElementById("7_4").classList.add("col_block_divider");
|
||||
document.getElementById("7_9").classList.add("col_block_divider");
|
||||
document.getElementById("7_14").classList.add("col_block_divider");
|
||||
document.getElementById("7_19").classList.add("col_block_divider");
|
||||
document.getElementById("7_24").classList.add("col_block_divider");
|
||||
document.getElementById("8_4").classList.add("col_block_divider");
|
||||
document.getElementById("8_9").classList.add("col_block_divider");
|
||||
document.getElementById("8_14").classList.add("col_block_divider");
|
||||
document.getElementById("8_19").classList.add("col_block_divider");
|
||||
document.getElementById("8_24").classList.add("col_block_divider");
|
||||
document.getElementById("9_4").classList.add("col_block_divider");
|
||||
document.getElementById("9_9").classList.add("col_block_divider");
|
||||
document.getElementById("9_14").classList.add("col_block_divider");
|
||||
document.getElementById("9_19").classList.add("col_block_divider");
|
||||
document.getElementById("9_24").classList.add("col_block_divider");
|
||||
document.getElementById("10_4").classList.add("col_block_divider");
|
||||
document.getElementById("10_9").classList.add("col_block_divider");
|
||||
document.getElementById("10_14").classList.add("col_block_divider");
|
||||
document.getElementById("10_19").classList.add("col_block_divider");
|
||||
document.getElementById("10_24").classList.add("col_block_divider");
|
||||
document.getElementById("11_4").classList.add("col_block_divider");
|
||||
document.getElementById("11_9").classList.add("col_block_divider");
|
||||
document.getElementById("11_14").classList.add("col_block_divider");
|
||||
document.getElementById("11_19").classList.add("col_block_divider");
|
||||
document.getElementById("11_24").classList.add("col_block_divider");
|
||||
document.getElementById("12_4").classList.add("col_block_divider");
|
||||
document.getElementById("12_9").classList.add("col_block_divider");
|
||||
document.getElementById("12_14").classList.add("col_block_divider");
|
||||
document.getElementById("12_19").classList.add("col_block_divider");
|
||||
document.getElementById("12_24").classList.add("col_block_divider");
|
||||
document.getElementById("13_4").classList.add("col_block_divider");
|
||||
document.getElementById("13_9").classList.add("col_block_divider");
|
||||
document.getElementById("13_14").classList.add("col_block_divider");
|
||||
document.getElementById("13_19").classList.add("col_block_divider");
|
||||
document.getElementById("13_24").classList.add("col_block_divider");
|
||||
document.getElementById("14_4").classList.add("col_block_divider");
|
||||
document.getElementById("14_9").classList.add("col_block_divider");
|
||||
document.getElementById("14_14").classList.add("col_block_divider");
|
||||
document.getElementById("14_19").classList.add("col_block_divider");
|
||||
document.getElementById("14_24").classList.add("col_block_divider");
|
||||
document.getElementById("15_4").classList.add("col_block_divider");
|
||||
document.getElementById("15_9").classList.add("col_block_divider");
|
||||
document.getElementById("15_14").classList.add("col_block_divider");
|
||||
document.getElementById("15_19").classList.add("col_block_divider");
|
||||
document.getElementById("15_24").classList.add("col_block_divider");
|
||||
document.getElementById("16_4").classList.add("col_block_divider");
|
||||
document.getElementById("16_9").classList.add("col_block_divider");
|
||||
document.getElementById("16_14").classList.add("col_block_divider");
|
||||
document.getElementById("16_19").classList.add("col_block_divider");
|
||||
document.getElementById("16_24").classList.add("col_block_divider");
|
||||
document.getElementById("17_4").classList.add("col_block_divider");
|
||||
document.getElementById("17_9").classList.add("col_block_divider");
|
||||
document.getElementById("17_14").classList.add("col_block_divider");
|
||||
document.getElementById("17_19").classList.add("col_block_divider");
|
||||
document.getElementById("17_24").classList.add("col_block_divider");
|
||||
document.getElementById("18_4").classList.add("col_block_divider");
|
||||
document.getElementById("18_9").classList.add("col_block_divider");
|
||||
document.getElementById("18_14").classList.add("col_block_divider");
|
||||
document.getElementById("18_19").classList.add("col_block_divider");
|
||||
document.getElementById("18_24").classList.add("col_block_divider");
|
||||
document.getElementById("19_4").classList.add("col_block_divider");
|
||||
document.getElementById("19_9").classList.add("col_block_divider");
|
||||
document.getElementById("19_14").classList.add("col_block_divider");
|
||||
document.getElementById("19_19").classList.add("col_block_divider");
|
||||
document.getElementById("19_24").classList.add("col_block_divider");
|
||||
document.getElementById("20_4").classList.add("col_block_divider");
|
||||
document.getElementById("20_9").classList.add("col_block_divider");
|
||||
document.getElementById("20_14").classList.add("col_block_divider");
|
||||
document.getElementById("20_19").classList.add("col_block_divider");
|
||||
document.getElementById("20_24").classList.add("col_block_divider");
|
||||
document.getElementById("21_4").classList.add("col_block_divider");
|
||||
document.getElementById("21_9").classList.add("col_block_divider");
|
||||
document.getElementById("21_14").classList.add("col_block_divider");
|
||||
document.getElementById("21_19").classList.add("col_block_divider");
|
||||
document.getElementById("21_24").classList.add("col_block_divider");
|
||||
document.getElementById("22_4").classList.add("col_block_divider");
|
||||
document.getElementById("22_9").classList.add("col_block_divider");
|
||||
document.getElementById("22_14").classList.add("col_block_divider");
|
||||
document.getElementById("22_19").classList.add("col_block_divider");
|
||||
document.getElementById("22_24").classList.add("col_block_divider");
|
||||
document.getElementById("23_4").classList.add("col_block_divider");
|
||||
document.getElementById("23_9").classList.add("col_block_divider");
|
||||
document.getElementById("23_14").classList.add("col_block_divider");
|
||||
document.getElementById("23_19").classList.add("col_block_divider");
|
||||
document.getElementById("23_24").classList.add("col_block_divider");
|
||||
document.getElementById("24_4").classList.add("col_block_divider");
|
||||
document.getElementById("24_9").classList.add("col_block_divider");
|
||||
document.getElementById("24_14").classList.add("col_block_divider");
|
||||
document.getElementById("24_19").classList.add("col_block_divider");
|
||||
document.getElementById("24_24").classList.add("col_block_divider");
|
||||
document.getElementById("4_0").classList.add("row_block_divider");
|
||||
document.getElementById("4_1").classList.add("row_block_divider");
|
||||
document.getElementById("4_2").classList.add("row_block_divider");
|
||||
document.getElementById("4_3").classList.add("row_block_divider");
|
||||
document.getElementById("4_4").classList.add("row_block_divider");
|
||||
document.getElementById("4_5").classList.add("row_block_divider");
|
||||
document.getElementById("4_6").classList.add("row_block_divider");
|
||||
document.getElementById("4_7").classList.add("row_block_divider");
|
||||
document.getElementById("4_8").classList.add("row_block_divider");
|
||||
document.getElementById("4_9").classList.add("row_block_divider");
|
||||
document.getElementById("4_10").classList.add("row_block_divider");
|
||||
document.getElementById("4_11").classList.add("row_block_divider");
|
||||
document.getElementById("4_12").classList.add("row_block_divider");
|
||||
document.getElementById("4_13").classList.add("row_block_divider");
|
||||
document.getElementById("4_14").classList.add("row_block_divider");
|
||||
document.getElementById("4_15").classList.add("row_block_divider");
|
||||
document.getElementById("4_16").classList.add("row_block_divider");
|
||||
document.getElementById("4_17").classList.add("row_block_divider");
|
||||
document.getElementById("4_18").classList.add("row_block_divider");
|
||||
document.getElementById("4_19").classList.add("row_block_divider");
|
||||
document.getElementById("4_20").classList.add("row_block_divider");
|
||||
document.getElementById("4_21").classList.add("row_block_divider");
|
||||
document.getElementById("4_22").classList.add("row_block_divider");
|
||||
document.getElementById("4_23").classList.add("row_block_divider");
|
||||
document.getElementById("4_24").classList.add("row_block_divider");
|
||||
document.getElementById("9_0").classList.add("row_block_divider");
|
||||
document.getElementById("9_1").classList.add("row_block_divider");
|
||||
document.getElementById("9_2").classList.add("row_block_divider");
|
||||
document.getElementById("9_3").classList.add("row_block_divider");
|
||||
document.getElementById("9_4").classList.add("row_block_divider");
|
||||
document.getElementById("9_5").classList.add("row_block_divider");
|
||||
document.getElementById("9_6").classList.add("row_block_divider");
|
||||
document.getElementById("9_7").classList.add("row_block_divider");
|
||||
document.getElementById("9_8").classList.add("row_block_divider");
|
||||
document.getElementById("9_9").classList.add("row_block_divider");
|
||||
document.getElementById("9_10").classList.add("row_block_divider");
|
||||
document.getElementById("9_11").classList.add("row_block_divider");
|
||||
document.getElementById("9_12").classList.add("row_block_divider");
|
||||
document.getElementById("9_13").classList.add("row_block_divider");
|
||||
document.getElementById("9_14").classList.add("row_block_divider");
|
||||
document.getElementById("9_15").classList.add("row_block_divider");
|
||||
document.getElementById("9_16").classList.add("row_block_divider");
|
||||
document.getElementById("9_17").classList.add("row_block_divider");
|
||||
document.getElementById("9_18").classList.add("row_block_divider");
|
||||
document.getElementById("9_19").classList.add("row_block_divider");
|
||||
document.getElementById("9_20").classList.add("row_block_divider");
|
||||
document.getElementById("9_21").classList.add("row_block_divider");
|
||||
document.getElementById("9_22").classList.add("row_block_divider");
|
||||
document.getElementById("9_23").classList.add("row_block_divider");
|
||||
document.getElementById("9_24").classList.add("row_block_divider");
|
||||
document.getElementById("14_0").classList.add("row_block_divider");
|
||||
document.getElementById("14_1").classList.add("row_block_divider");
|
||||
document.getElementById("14_2").classList.add("row_block_divider");
|
||||
document.getElementById("14_3").classList.add("row_block_divider");
|
||||
document.getElementById("14_4").classList.add("row_block_divider");
|
||||
document.getElementById("14_5").classList.add("row_block_divider");
|
||||
document.getElementById("14_6").classList.add("row_block_divider");
|
||||
document.getElementById("14_7").classList.add("row_block_divider");
|
||||
document.getElementById("14_8").classList.add("row_block_divider");
|
||||
document.getElementById("14_9").classList.add("row_block_divider");
|
||||
document.getElementById("14_10").classList.add("row_block_divider");
|
||||
document.getElementById("14_11").classList.add("row_block_divider");
|
||||
document.getElementById("14_12").classList.add("row_block_divider");
|
||||
document.getElementById("14_13").classList.add("row_block_divider");
|
||||
document.getElementById("14_14").classList.add("row_block_divider");
|
||||
document.getElementById("14_15").classList.add("row_block_divider");
|
||||
document.getElementById("14_16").classList.add("row_block_divider");
|
||||
document.getElementById("14_17").classList.add("row_block_divider");
|
||||
document.getElementById("14_18").classList.add("row_block_divider");
|
||||
document.getElementById("14_19").classList.add("row_block_divider");
|
||||
document.getElementById("14_20").classList.add("row_block_divider");
|
||||
document.getElementById("14_21").classList.add("row_block_divider");
|
||||
document.getElementById("14_22").classList.add("row_block_divider");
|
||||
document.getElementById("14_23").classList.add("row_block_divider");
|
||||
document.getElementById("14_24").classList.add("row_block_divider");
|
||||
document.getElementById("19_0").classList.add("row_block_divider");
|
||||
document.getElementById("19_1").classList.add("row_block_divider");
|
||||
document.getElementById("19_2").classList.add("row_block_divider");
|
||||
document.getElementById("19_3").classList.add("row_block_divider");
|
||||
document.getElementById("19_4").classList.add("row_block_divider");
|
||||
document.getElementById("19_5").classList.add("row_block_divider");
|
||||
document.getElementById("19_6").classList.add("row_block_divider");
|
||||
document.getElementById("19_7").classList.add("row_block_divider");
|
||||
document.getElementById("19_8").classList.add("row_block_divider");
|
||||
document.getElementById("19_9").classList.add("row_block_divider");
|
||||
document.getElementById("19_10").classList.add("row_block_divider");
|
||||
document.getElementById("19_11").classList.add("row_block_divider");
|
||||
document.getElementById("19_12").classList.add("row_block_divider");
|
||||
document.getElementById("19_13").classList.add("row_block_divider");
|
||||
document.getElementById("19_14").classList.add("row_block_divider");
|
||||
document.getElementById("19_15").classList.add("row_block_divider");
|
||||
document.getElementById("19_16").classList.add("row_block_divider");
|
||||
document.getElementById("19_17").classList.add("row_block_divider");
|
||||
document.getElementById("19_18").classList.add("row_block_divider");
|
||||
document.getElementById("19_19").classList.add("row_block_divider");
|
||||
document.getElementById("19_20").classList.add("row_block_divider");
|
||||
document.getElementById("19_21").classList.add("row_block_divider");
|
||||
document.getElementById("19_22").classList.add("row_block_divider");
|
||||
document.getElementById("19_23").classList.add("row_block_divider");
|
||||
document.getElementById("19_24").classList.add("row_block_divider");
|
||||
document.getElementById("24_0").classList.add("row_block_divider");
|
||||
document.getElementById("24_1").classList.add("row_block_divider");
|
||||
document.getElementById("24_2").classList.add("row_block_divider");
|
||||
document.getElementById("24_3").classList.add("row_block_divider");
|
||||
document.getElementById("24_4").classList.add("row_block_divider");
|
||||
document.getElementById("24_5").classList.add("row_block_divider");
|
||||
document.getElementById("24_6").classList.add("row_block_divider");
|
||||
document.getElementById("24_7").classList.add("row_block_divider");
|
||||
document.getElementById("24_8").classList.add("row_block_divider");
|
||||
document.getElementById("24_9").classList.add("row_block_divider");
|
||||
document.getElementById("24_10").classList.add("row_block_divider");
|
||||
document.getElementById("24_11").classList.add("row_block_divider");
|
||||
document.getElementById("24_12").classList.add("row_block_divider");
|
||||
document.getElementById("24_13").classList.add("row_block_divider");
|
||||
document.getElementById("24_14").classList.add("row_block_divider");
|
||||
document.getElementById("24_15").classList.add("row_block_divider");
|
||||
document.getElementById("24_16").classList.add("row_block_divider");
|
||||
document.getElementById("24_17").classList.add("row_block_divider");
|
||||
document.getElementById("24_18").classList.add("row_block_divider");
|
||||
document.getElementById("24_19").classList.add("row_block_divider");
|
||||
document.getElementById("24_20").classList.add("row_block_divider");
|
||||
document.getElementById("24_21").classList.add("row_block_divider");
|
||||
document.getElementById("24_22").classList.add("row_block_divider");
|
||||
document.getElementById("24_23").classList.add("row_block_divider");
|
||||
document.getElementById("24_24").classList.add("row_block_divider");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,803 @@
|
||||
|
||||
<html>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin-top: 5em;
|
||||
text-align: center;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
.title {
|
||||
margin-top: 0.5em;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 1em
|
||||
}
|
||||
table {
|
||||
margin-top: 1em;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px dotted #c8c8c8;
|
||||
width: 11px;
|
||||
height: 12px;
|
||||
}
|
||||
.qr_table {
|
||||
margin-top: 5em;
|
||||
margin-right: 4em;
|
||||
float: right;
|
||||
}
|
||||
.filled {
|
||||
background-color: black;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.col_name, .row_name {
|
||||
text-align: center;
|
||||
font-size: 0.7em;
|
||||
color: #aaa;
|
||||
}
|
||||
.col_name {
|
||||
border-bottom: 1px solid #bbb;
|
||||
height: 2em;
|
||||
}
|
||||
.row_name {
|
||||
border-right: 1px solid #bbb;
|
||||
width: 2em;
|
||||
}
|
||||
.col_block_divider {
|
||||
border-right: 1px solid #bbb;
|
||||
}
|
||||
.row_block_divider {
|
||||
border-bottom: 1px solid #bbb;
|
||||
}
|
||||
.no_border {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.word_list_table {
|
||||
margin-left: 0;
|
||||
|
||||
float: left;
|
||||
}
|
||||
.word_list {
|
||||
text-align: right;
|
||||
padding-top: 2em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.word_row {
|
||||
height: 2.5em;
|
||||
}
|
||||
.word_num {
|
||||
width: 1em;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
}
|
||||
.word_blank {
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
|
||||
<table align="center" class="word_list_table">
|
||||
<tr>
|
||||
<td class="word_list">
|
||||
<div class="word_row">
|
||||
<span class="word_num">1: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">2: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">3: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">4: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">5: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">6: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">7: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">8: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">9: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">10: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">11: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">12: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td class="word_list">
|
||||
<div class="word_row">
|
||||
<span class="word_num">13: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">14: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">15: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">16: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">17: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">18: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">19: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">20: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">21: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">22: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">23: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
<div class="word_row">
|
||||
<span class="word_num">24: </span><span class="word_blank">____________________________</span>
|
||||
</div>
|
||||
</td></tr></table><table align="center" class="qr_table"><tr rowspan="5"><td class="col_block_divider row_block_divider"></td><td colspan="5" class="col_name col_block_divider">1</td><td colspan="5" class="col_name col_block_divider">2</td><td colspan="5" class="col_name col_block_divider">3</td><td colspan="5" class="col_name col_block_divider">4</td><td colspan="5" class="col_name col_block_divider">5</td><td colspan="5" class="col_name col_block_divider">6</td></tr><tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">A</td><td id="0_0"></td><td id="0_1"></td><td id="0_2"></td><td id="0_3"></td><td id="0_4"></td><td id="0_5"></td><td id="0_6"></td><td id="0_7"></td><td id="0_8"></td><td id="0_9"></td><td id="0_10"></td><td id="0_11"></td><td id="0_12"></td><td id="0_13"></td><td id="0_14"></td><td id="0_15"></td><td id="0_16"></td><td id="0_17"></td><td id="0_18"></td><td id="0_19"></td><td id="0_20"></td><td id="0_21"></td><td id="0_22"></td><td id="0_23"></td><td id="0_24"></td><td id="0_25"></td><td id="0_26"></td><td id="0_27"></td><td id="0_28"></td></tr>
|
||||
<tr>
|
||||
<td id="1_0"></td><td id="1_1"></td><td id="1_2"></td><td id="1_3"></td><td id="1_4"></td><td id="1_5"></td><td id="1_6"></td><td id="1_7"></td><td id="1_8"></td><td id="1_9"></td><td id="1_10"></td><td id="1_11"></td><td id="1_12"></td><td id="1_13"></td><td id="1_14"></td><td id="1_15"></td><td id="1_16"></td><td id="1_17"></td><td id="1_18"></td><td id="1_19"></td><td id="1_20"></td><td id="1_21"></td><td id="1_22"></td><td id="1_23"></td><td id="1_24"></td><td id="1_25"></td><td id="1_26"></td><td id="1_27"></td><td id="1_28"></td></tr>
|
||||
<tr>
|
||||
<td id="2_0"></td><td id="2_1"></td><td id="2_2"></td><td id="2_3"></td><td id="2_4"></td><td id="2_5"></td><td id="2_6"></td><td id="2_7"></td><td id="2_8"></td><td id="2_9"></td><td id="2_10"></td><td id="2_11"></td><td id="2_12"></td><td id="2_13"></td><td id="2_14"></td><td id="2_15"></td><td id="2_16"></td><td id="2_17"></td><td id="2_18"></td><td id="2_19"></td><td id="2_20"></td><td id="2_21"></td><td id="2_22"></td><td id="2_23"></td><td id="2_24"></td><td id="2_25"></td><td id="2_26"></td><td id="2_27"></td><td id="2_28"></td></tr>
|
||||
<tr>
|
||||
<td id="3_0"></td><td id="3_1"></td><td id="3_2"></td><td id="3_3"></td><td id="3_4"></td><td id="3_5"></td><td id="3_6"></td><td id="3_7"></td><td id="3_8"></td><td id="3_9"></td><td id="3_10"></td><td id="3_11"></td><td id="3_12"></td><td id="3_13"></td><td id="3_14"></td><td id="3_15"></td><td id="3_16"></td><td id="3_17"></td><td id="3_18"></td><td id="3_19"></td><td id="3_20"></td><td id="3_21"></td><td id="3_22"></td><td id="3_23"></td><td id="3_24"></td><td id="3_25"></td><td id="3_26"></td><td id="3_27"></td><td id="3_28"></td></tr>
|
||||
<tr>
|
||||
<td id="4_0"></td><td id="4_1"></td><td id="4_2"></td><td id="4_3"></td><td id="4_4"></td><td id="4_5"></td><td id="4_6"></td><td id="4_7"></td><td id="4_8"></td><td id="4_9"></td><td id="4_10"></td><td id="4_11"></td><td id="4_12"></td><td id="4_13"></td><td id="4_14"></td><td id="4_15"></td><td id="4_16"></td><td id="4_17"></td><td id="4_18"></td><td id="4_19"></td><td id="4_20"></td><td id="4_21"></td><td id="4_22"></td><td id="4_23"></td><td id="4_24"></td><td id="4_25"></td><td id="4_26"></td><td id="4_27"></td><td id="4_28"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">B</td><td id="5_0"></td><td id="5_1"></td><td id="5_2"></td><td id="5_3"></td><td id="5_4"></td><td id="5_5"></td><td id="5_6"></td><td id="5_7"></td><td id="5_8"></td><td id="5_9"></td><td id="5_10"></td><td id="5_11"></td><td id="5_12"></td><td id="5_13"></td><td id="5_14"></td><td id="5_15"></td><td id="5_16"></td><td id="5_17"></td><td id="5_18"></td><td id="5_19"></td><td id="5_20"></td><td id="5_21"></td><td id="5_22"></td><td id="5_23"></td><td id="5_24"></td><td id="5_25"></td><td id="5_26"></td><td id="5_27"></td><td id="5_28"></td></tr>
|
||||
<tr>
|
||||
<td id="6_0"></td><td id="6_1"></td><td id="6_2"></td><td id="6_3"></td><td id="6_4"></td><td id="6_5"></td><td id="6_6"></td><td id="6_7"></td><td id="6_8"></td><td id="6_9"></td><td id="6_10"></td><td id="6_11"></td><td id="6_12"></td><td id="6_13"></td><td id="6_14"></td><td id="6_15"></td><td id="6_16"></td><td id="6_17"></td><td id="6_18"></td><td id="6_19"></td><td id="6_20"></td><td id="6_21"></td><td id="6_22"></td><td id="6_23"></td><td id="6_24"></td><td id="6_25"></td><td id="6_26"></td><td id="6_27"></td><td id="6_28"></td></tr>
|
||||
<tr>
|
||||
<td id="7_0"></td><td id="7_1"></td><td id="7_2"></td><td id="7_3"></td><td id="7_4"></td><td id="7_5"></td><td id="7_6"></td><td id="7_7"></td><td id="7_8"></td><td id="7_9"></td><td id="7_10"></td><td id="7_11"></td><td id="7_12"></td><td id="7_13"></td><td id="7_14"></td><td id="7_15"></td><td id="7_16"></td><td id="7_17"></td><td id="7_18"></td><td id="7_19"></td><td id="7_20"></td><td id="7_21"></td><td id="7_22"></td><td id="7_23"></td><td id="7_24"></td><td id="7_25"></td><td id="7_26"></td><td id="7_27"></td><td id="7_28"></td></tr>
|
||||
<tr>
|
||||
<td id="8_0"></td><td id="8_1"></td><td id="8_2"></td><td id="8_3"></td><td id="8_4"></td><td id="8_5"></td><td id="8_6"></td><td id="8_7"></td><td id="8_8"></td><td id="8_9"></td><td id="8_10"></td><td id="8_11"></td><td id="8_12"></td><td id="8_13"></td><td id="8_14"></td><td id="8_15"></td><td id="8_16"></td><td id="8_17"></td><td id="8_18"></td><td id="8_19"></td><td id="8_20"></td><td id="8_21"></td><td id="8_22"></td><td id="8_23"></td><td id="8_24"></td><td id="8_25"></td><td id="8_26"></td><td id="8_27"></td><td id="8_28"></td></tr>
|
||||
<tr>
|
||||
<td id="9_0"></td><td id="9_1"></td><td id="9_2"></td><td id="9_3"></td><td id="9_4"></td><td id="9_5"></td><td id="9_6"></td><td id="9_7"></td><td id="9_8"></td><td id="9_9"></td><td id="9_10"></td><td id="9_11"></td><td id="9_12"></td><td id="9_13"></td><td id="9_14"></td><td id="9_15"></td><td id="9_16"></td><td id="9_17"></td><td id="9_18"></td><td id="9_19"></td><td id="9_20"></td><td id="9_21"></td><td id="9_22"></td><td id="9_23"></td><td id="9_24"></td><td id="9_25"></td><td id="9_26"></td><td id="9_27"></td><td id="9_28"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">C</td><td id="10_0"></td><td id="10_1"></td><td id="10_2"></td><td id="10_3"></td><td id="10_4"></td><td id="10_5"></td><td id="10_6"></td><td id="10_7"></td><td id="10_8"></td><td id="10_9"></td><td id="10_10"></td><td id="10_11"></td><td id="10_12"></td><td id="10_13"></td><td id="10_14"></td><td id="10_15"></td><td id="10_16"></td><td id="10_17"></td><td id="10_18"></td><td id="10_19"></td><td id="10_20"></td><td id="10_21"></td><td id="10_22"></td><td id="10_23"></td><td id="10_24"></td><td id="10_25"></td><td id="10_26"></td><td id="10_27"></td><td id="10_28"></td></tr>
|
||||
<tr>
|
||||
<td id="11_0"></td><td id="11_1"></td><td id="11_2"></td><td id="11_3"></td><td id="11_4"></td><td id="11_5"></td><td id="11_6"></td><td id="11_7"></td><td id="11_8"></td><td id="11_9"></td><td id="11_10"></td><td id="11_11"></td><td id="11_12"></td><td id="11_13"></td><td id="11_14"></td><td id="11_15"></td><td id="11_16"></td><td id="11_17"></td><td id="11_18"></td><td id="11_19"></td><td id="11_20"></td><td id="11_21"></td><td id="11_22"></td><td id="11_23"></td><td id="11_24"></td><td id="11_25"></td><td id="11_26"></td><td id="11_27"></td><td id="11_28"></td></tr>
|
||||
<tr>
|
||||
<td id="12_0"></td><td id="12_1"></td><td id="12_2"></td><td id="12_3"></td><td id="12_4"></td><td id="12_5"></td><td id="12_6"></td><td id="12_7"></td><td id="12_8"></td><td id="12_9"></td><td id="12_10"></td><td id="12_11"></td><td id="12_12"></td><td id="12_13"></td><td id="12_14"></td><td id="12_15"></td><td id="12_16"></td><td id="12_17"></td><td id="12_18"></td><td id="12_19"></td><td id="12_20"></td><td id="12_21"></td><td id="12_22"></td><td id="12_23"></td><td id="12_24"></td><td id="12_25"></td><td id="12_26"></td><td id="12_27"></td><td id="12_28"></td></tr>
|
||||
<tr>
|
||||
<td id="13_0"></td><td id="13_1"></td><td id="13_2"></td><td id="13_3"></td><td id="13_4"></td><td id="13_5"></td><td id="13_6"></td><td id="13_7"></td><td id="13_8"></td><td id="13_9"></td><td id="13_10"></td><td id="13_11"></td><td id="13_12"></td><td id="13_13"></td><td id="13_14"></td><td id="13_15"></td><td id="13_16"></td><td id="13_17"></td><td id="13_18"></td><td id="13_19"></td><td id="13_20"></td><td id="13_21"></td><td id="13_22"></td><td id="13_23"></td><td id="13_24"></td><td id="13_25"></td><td id="13_26"></td><td id="13_27"></td><td id="13_28"></td></tr>
|
||||
<tr>
|
||||
<td id="14_0"></td><td id="14_1"></td><td id="14_2"></td><td id="14_3"></td><td id="14_4"></td><td id="14_5"></td><td id="14_6"></td><td id="14_7"></td><td id="14_8"></td><td id="14_9"></td><td id="14_10"></td><td id="14_11"></td><td id="14_12"></td><td id="14_13"></td><td id="14_14"></td><td id="14_15"></td><td id="14_16"></td><td id="14_17"></td><td id="14_18"></td><td id="14_19"></td><td id="14_20"></td><td id="14_21"></td><td id="14_22"></td><td id="14_23"></td><td id="14_24"></td><td id="14_25"></td><td id="14_26"></td><td id="14_27"></td><td id="14_28"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">D</td><td id="15_0"></td><td id="15_1"></td><td id="15_2"></td><td id="15_3"></td><td id="15_4"></td><td id="15_5"></td><td id="15_6"></td><td id="15_7"></td><td id="15_8"></td><td id="15_9"></td><td id="15_10"></td><td id="15_11"></td><td id="15_12"></td><td id="15_13"></td><td id="15_14"></td><td id="15_15"></td><td id="15_16"></td><td id="15_17"></td><td id="15_18"></td><td id="15_19"></td><td id="15_20"></td><td id="15_21"></td><td id="15_22"></td><td id="15_23"></td><td id="15_24"></td><td id="15_25"></td><td id="15_26"></td><td id="15_27"></td><td id="15_28"></td></tr>
|
||||
<tr>
|
||||
<td id="16_0"></td><td id="16_1"></td><td id="16_2"></td><td id="16_3"></td><td id="16_4"></td><td id="16_5"></td><td id="16_6"></td><td id="16_7"></td><td id="16_8"></td><td id="16_9"></td><td id="16_10"></td><td id="16_11"></td><td id="16_12"></td><td id="16_13"></td><td id="16_14"></td><td id="16_15"></td><td id="16_16"></td><td id="16_17"></td><td id="16_18"></td><td id="16_19"></td><td id="16_20"></td><td id="16_21"></td><td id="16_22"></td><td id="16_23"></td><td id="16_24"></td><td id="16_25"></td><td id="16_26"></td><td id="16_27"></td><td id="16_28"></td></tr>
|
||||
<tr>
|
||||
<td id="17_0"></td><td id="17_1"></td><td id="17_2"></td><td id="17_3"></td><td id="17_4"></td><td id="17_5"></td><td id="17_6"></td><td id="17_7"></td><td id="17_8"></td><td id="17_9"></td><td id="17_10"></td><td id="17_11"></td><td id="17_12"></td><td id="17_13"></td><td id="17_14"></td><td id="17_15"></td><td id="17_16"></td><td id="17_17"></td><td id="17_18"></td><td id="17_19"></td><td id="17_20"></td><td id="17_21"></td><td id="17_22"></td><td id="17_23"></td><td id="17_24"></td><td id="17_25"></td><td id="17_26"></td><td id="17_27"></td><td id="17_28"></td></tr>
|
||||
<tr>
|
||||
<td id="18_0"></td><td id="18_1"></td><td id="18_2"></td><td id="18_3"></td><td id="18_4"></td><td id="18_5"></td><td id="18_6"></td><td id="18_7"></td><td id="18_8"></td><td id="18_9"></td><td id="18_10"></td><td id="18_11"></td><td id="18_12"></td><td id="18_13"></td><td id="18_14"></td><td id="18_15"></td><td id="18_16"></td><td id="18_17"></td><td id="18_18"></td><td id="18_19"></td><td id="18_20"></td><td id="18_21"></td><td id="18_22"></td><td id="18_23"></td><td id="18_24"></td><td id="18_25"></td><td id="18_26"></td><td id="18_27"></td><td id="18_28"></td></tr>
|
||||
<tr>
|
||||
<td id="19_0"></td><td id="19_1"></td><td id="19_2"></td><td id="19_3"></td><td id="19_4"></td><td id="19_5"></td><td id="19_6"></td><td id="19_7"></td><td id="19_8"></td><td id="19_9"></td><td id="19_10"></td><td id="19_11"></td><td id="19_12"></td><td id="19_13"></td><td id="19_14"></td><td id="19_15"></td><td id="19_16"></td><td id="19_17"></td><td id="19_18"></td><td id="19_19"></td><td id="19_20"></td><td id="19_21"></td><td id="19_22"></td><td id="19_23"></td><td id="19_24"></td><td id="19_25"></td><td id="19_26"></td><td id="19_27"></td><td id="19_28"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">E</td><td id="20_0"></td><td id="20_1"></td><td id="20_2"></td><td id="20_3"></td><td id="20_4"></td><td id="20_5"></td><td id="20_6"></td><td id="20_7"></td><td id="20_8"></td><td id="20_9"></td><td id="20_10"></td><td id="20_11"></td><td id="20_12"></td><td id="20_13"></td><td id="20_14"></td><td id="20_15"></td><td id="20_16"></td><td id="20_17"></td><td id="20_18"></td><td id="20_19"></td><td id="20_20"></td><td id="20_21"></td><td id="20_22"></td><td id="20_23"></td><td id="20_24"></td><td id="20_25"></td><td id="20_26"></td><td id="20_27"></td><td id="20_28"></td></tr>
|
||||
<tr>
|
||||
<td id="21_0"></td><td id="21_1"></td><td id="21_2"></td><td id="21_3"></td><td id="21_4"></td><td id="21_5"></td><td id="21_6"></td><td id="21_7"></td><td id="21_8"></td><td id="21_9"></td><td id="21_10"></td><td id="21_11"></td><td id="21_12"></td><td id="21_13"></td><td id="21_14"></td><td id="21_15"></td><td id="21_16"></td><td id="21_17"></td><td id="21_18"></td><td id="21_19"></td><td id="21_20"></td><td id="21_21"></td><td id="21_22"></td><td id="21_23"></td><td id="21_24"></td><td id="21_25"></td><td id="21_26"></td><td id="21_27"></td><td id="21_28"></td></tr>
|
||||
<tr>
|
||||
<td id="22_0"></td><td id="22_1"></td><td id="22_2"></td><td id="22_3"></td><td id="22_4"></td><td id="22_5"></td><td id="22_6"></td><td id="22_7"></td><td id="22_8"></td><td id="22_9"></td><td id="22_10"></td><td id="22_11"></td><td id="22_12"></td><td id="22_13"></td><td id="22_14"></td><td id="22_15"></td><td id="22_16"></td><td id="22_17"></td><td id="22_18"></td><td id="22_19"></td><td id="22_20"></td><td id="22_21"></td><td id="22_22"></td><td id="22_23"></td><td id="22_24"></td><td id="22_25"></td><td id="22_26"></td><td id="22_27"></td><td id="22_28"></td></tr>
|
||||
<tr>
|
||||
<td id="23_0"></td><td id="23_1"></td><td id="23_2"></td><td id="23_3"></td><td id="23_4"></td><td id="23_5"></td><td id="23_6"></td><td id="23_7"></td><td id="23_8"></td><td id="23_9"></td><td id="23_10"></td><td id="23_11"></td><td id="23_12"></td><td id="23_13"></td><td id="23_14"></td><td id="23_15"></td><td id="23_16"></td><td id="23_17"></td><td id="23_18"></td><td id="23_19"></td><td id="23_20"></td><td id="23_21"></td><td id="23_22"></td><td id="23_23"></td><td id="23_24"></td><td id="23_25"></td><td id="23_26"></td><td id="23_27"></td><td id="23_28"></td></tr>
|
||||
<tr>
|
||||
<td id="24_0"></td><td id="24_1"></td><td id="24_2"></td><td id="24_3"></td><td id="24_4"></td><td id="24_5"></td><td id="24_6"></td><td id="24_7"></td><td id="24_8"></td><td id="24_9"></td><td id="24_10"></td><td id="24_11"></td><td id="24_12"></td><td id="24_13"></td><td id="24_14"></td><td id="24_15"></td><td id="24_16"></td><td id="24_17"></td><td id="24_18"></td><td id="24_19"></td><td id="24_20"></td><td id="24_21"></td><td id="24_22"></td><td id="24_23"></td><td id="24_24"></td><td id="24_25"></td><td id="24_26"></td><td id="24_27"></td><td id="24_28"></td></tr>
|
||||
<tr>
|
||||
<td rowspan="5" class="row_name row_block_divider">F</td><td id="25_0"></td><td id="25_1"></td><td id="25_2"></td><td id="25_3"></td><td id="25_4"></td><td id="25_5"></td><td id="25_6"></td><td id="25_7"></td><td id="25_8"></td><td id="25_9"></td><td id="25_10"></td><td id="25_11"></td><td id="25_12"></td><td id="25_13"></td><td id="25_14"></td><td id="25_15"></td><td id="25_16"></td><td id="25_17"></td><td id="25_18"></td><td id="25_19"></td><td id="25_20"></td><td id="25_21"></td><td id="25_22"></td><td id="25_23"></td><td id="25_24"></td><td id="25_25"></td><td id="25_26"></td><td id="25_27"></td><td id="25_28"></td></tr>
|
||||
<tr>
|
||||
<td id="26_0"></td><td id="26_1"></td><td id="26_2"></td><td id="26_3"></td><td id="26_4"></td><td id="26_5"></td><td id="26_6"></td><td id="26_7"></td><td id="26_8"></td><td id="26_9"></td><td id="26_10"></td><td id="26_11"></td><td id="26_12"></td><td id="26_13"></td><td id="26_14"></td><td id="26_15"></td><td id="26_16"></td><td id="26_17"></td><td id="26_18"></td><td id="26_19"></td><td id="26_20"></td><td id="26_21"></td><td id="26_22"></td><td id="26_23"></td><td id="26_24"></td><td id="26_25"></td><td id="26_26"></td><td id="26_27"></td><td id="26_28"></td></tr>
|
||||
<tr>
|
||||
<td id="27_0"></td><td id="27_1"></td><td id="27_2"></td><td id="27_3"></td><td id="27_4"></td><td id="27_5"></td><td id="27_6"></td><td id="27_7"></td><td id="27_8"></td><td id="27_9"></td><td id="27_10"></td><td id="27_11"></td><td id="27_12"></td><td id="27_13"></td><td id="27_14"></td><td id="27_15"></td><td id="27_16"></td><td id="27_17"></td><td id="27_18"></td><td id="27_19"></td><td id="27_20"></td><td id="27_21"></td><td id="27_22"></td><td id="27_23"></td><td id="27_24"></td><td id="27_25"></td><td id="27_26"></td><td id="27_27"></td><td id="27_28"></td></tr>
|
||||
<tr>
|
||||
<td id="28_0"></td><td id="28_1"></td><td id="28_2"></td><td id="28_3"></td><td id="28_4"></td><td id="28_5"></td><td id="28_6"></td><td id="28_7"></td><td id="28_8"></td><td id="28_9"></td><td id="28_10"></td><td id="28_11"></td><td id="28_12"></td><td id="28_13"></td><td id="28_14"></td><td id="28_15"></td><td id="28_16"></td><td id="28_17"></td><td id="28_18"></td><td id="28_19"></td><td id="28_20"></td><td id="28_21"></td><td id="28_22"></td><td id="28_23"></td><td id="28_24"></td><td id="28_25"></td><td id="28_26"></td><td id="28_27"></td><td id="28_28"></td></tr>
|
||||
</table><script>document.getElementById("0_0").classList.add("filled");
|
||||
document.getElementById("0_6").classList.add("filled");
|
||||
document.getElementById("1_0").classList.add("filled");
|
||||
document.getElementById("1_6").classList.add("filled");
|
||||
document.getElementById("2_0").classList.add("filled");
|
||||
document.getElementById("2_6").classList.add("filled");
|
||||
document.getElementById("3_0").classList.add("filled");
|
||||
document.getElementById("3_6").classList.add("filled");
|
||||
document.getElementById("4_0").classList.add("filled");
|
||||
document.getElementById("4_6").classList.add("filled");
|
||||
document.getElementById("5_0").classList.add("filled");
|
||||
document.getElementById("5_6").classList.add("filled");
|
||||
document.getElementById("6_0").classList.add("filled");
|
||||
document.getElementById("6_6").classList.add("filled");
|
||||
document.getElementById("0_1").classList.add("filled");
|
||||
document.getElementById("6_1").classList.add("filled");
|
||||
document.getElementById("0_2").classList.add("filled");
|
||||
document.getElementById("6_2").classList.add("filled");
|
||||
document.getElementById("0_3").classList.add("filled");
|
||||
document.getElementById("6_3").classList.add("filled");
|
||||
document.getElementById("0_4").classList.add("filled");
|
||||
document.getElementById("6_4").classList.add("filled");
|
||||
document.getElementById("0_5").classList.add("filled");
|
||||
document.getElementById("6_5").classList.add("filled");
|
||||
document.getElementById("2_2").classList.add("filled");
|
||||
document.getElementById("2_3").classList.add("filled");
|
||||
document.getElementById("2_4").classList.add("filled");
|
||||
document.getElementById("3_2").classList.add("filled");
|
||||
document.getElementById("3_3").classList.add("filled");
|
||||
document.getElementById("3_4").classList.add("filled");
|
||||
document.getElementById("4_2").classList.add("filled");
|
||||
document.getElementById("4_3").classList.add("filled");
|
||||
document.getElementById("4_4").classList.add("filled");
|
||||
document.getElementById("0_0").classList.add("no_border");
|
||||
document.getElementById("0_1").classList.add("no_border");
|
||||
document.getElementById("0_2").classList.add("no_border");
|
||||
document.getElementById("0_3").classList.add("no_border");
|
||||
document.getElementById("0_4").classList.add("no_border");
|
||||
document.getElementById("0_5").classList.add("no_border");
|
||||
document.getElementById("0_6").classList.add("no_border");
|
||||
document.getElementById("1_0").classList.add("no_border");
|
||||
document.getElementById("1_1").classList.add("no_border");
|
||||
document.getElementById("1_2").classList.add("no_border");
|
||||
document.getElementById("1_3").classList.add("no_border");
|
||||
document.getElementById("1_4").classList.add("no_border");
|
||||
document.getElementById("1_5").classList.add("no_border");
|
||||
document.getElementById("1_6").classList.add("no_border");
|
||||
document.getElementById("2_0").classList.add("no_border");
|
||||
document.getElementById("2_1").classList.add("no_border");
|
||||
document.getElementById("2_2").classList.add("no_border");
|
||||
document.getElementById("2_3").classList.add("no_border");
|
||||
document.getElementById("2_4").classList.add("no_border");
|
||||
document.getElementById("2_5").classList.add("no_border");
|
||||
document.getElementById("2_6").classList.add("no_border");
|
||||
document.getElementById("3_0").classList.add("no_border");
|
||||
document.getElementById("3_1").classList.add("no_border");
|
||||
document.getElementById("3_2").classList.add("no_border");
|
||||
document.getElementById("3_3").classList.add("no_border");
|
||||
document.getElementById("3_4").classList.add("no_border");
|
||||
document.getElementById("3_5").classList.add("no_border");
|
||||
document.getElementById("3_6").classList.add("no_border");
|
||||
document.getElementById("4_0").classList.add("no_border");
|
||||
document.getElementById("4_1").classList.add("no_border");
|
||||
document.getElementById("4_2").classList.add("no_border");
|
||||
document.getElementById("4_3").classList.add("no_border");
|
||||
document.getElementById("4_4").classList.add("no_border");
|
||||
document.getElementById("4_5").classList.add("no_border");
|
||||
document.getElementById("4_6").classList.add("no_border");
|
||||
document.getElementById("5_0").classList.add("no_border");
|
||||
document.getElementById("5_1").classList.add("no_border");
|
||||
document.getElementById("5_2").classList.add("no_border");
|
||||
document.getElementById("5_3").classList.add("no_border");
|
||||
document.getElementById("5_4").classList.add("no_border");
|
||||
document.getElementById("5_5").classList.add("no_border");
|
||||
document.getElementById("5_6").classList.add("no_border");
|
||||
document.getElementById("6_0").classList.add("no_border");
|
||||
document.getElementById("6_1").classList.add("no_border");
|
||||
document.getElementById("6_2").classList.add("no_border");
|
||||
document.getElementById("6_3").classList.add("no_border");
|
||||
document.getElementById("6_4").classList.add("no_border");
|
||||
document.getElementById("6_5").classList.add("no_border");
|
||||
document.getElementById("6_6").classList.add("no_border");
|
||||
document.getElementById("0_22").classList.add("filled");
|
||||
document.getElementById("0_28").classList.add("filled");
|
||||
document.getElementById("1_22").classList.add("filled");
|
||||
document.getElementById("1_28").classList.add("filled");
|
||||
document.getElementById("2_22").classList.add("filled");
|
||||
document.getElementById("2_28").classList.add("filled");
|
||||
document.getElementById("3_22").classList.add("filled");
|
||||
document.getElementById("3_28").classList.add("filled");
|
||||
document.getElementById("4_22").classList.add("filled");
|
||||
document.getElementById("4_28").classList.add("filled");
|
||||
document.getElementById("5_22").classList.add("filled");
|
||||
document.getElementById("5_28").classList.add("filled");
|
||||
document.getElementById("6_22").classList.add("filled");
|
||||
document.getElementById("6_28").classList.add("filled");
|
||||
document.getElementById("0_23").classList.add("filled");
|
||||
document.getElementById("6_23").classList.add("filled");
|
||||
document.getElementById("0_24").classList.add("filled");
|
||||
document.getElementById("6_24").classList.add("filled");
|
||||
document.getElementById("0_25").classList.add("filled");
|
||||
document.getElementById("6_25").classList.add("filled");
|
||||
document.getElementById("0_26").classList.add("filled");
|
||||
document.getElementById("6_26").classList.add("filled");
|
||||
document.getElementById("0_27").classList.add("filled");
|
||||
document.getElementById("6_27").classList.add("filled");
|
||||
document.getElementById("2_24").classList.add("filled");
|
||||
document.getElementById("2_25").classList.add("filled");
|
||||
document.getElementById("2_26").classList.add("filled");
|
||||
document.getElementById("3_24").classList.add("filled");
|
||||
document.getElementById("3_25").classList.add("filled");
|
||||
document.getElementById("3_26").classList.add("filled");
|
||||
document.getElementById("4_24").classList.add("filled");
|
||||
document.getElementById("4_25").classList.add("filled");
|
||||
document.getElementById("4_26").classList.add("filled");
|
||||
document.getElementById("0_22").classList.add("no_border");
|
||||
document.getElementById("0_23").classList.add("no_border");
|
||||
document.getElementById("0_24").classList.add("no_border");
|
||||
document.getElementById("0_25").classList.add("no_border");
|
||||
document.getElementById("0_26").classList.add("no_border");
|
||||
document.getElementById("0_27").classList.add("no_border");
|
||||
document.getElementById("0_28").classList.add("no_border");
|
||||
document.getElementById("1_22").classList.add("no_border");
|
||||
document.getElementById("1_23").classList.add("no_border");
|
||||
document.getElementById("1_24").classList.add("no_border");
|
||||
document.getElementById("1_25").classList.add("no_border");
|
||||
document.getElementById("1_26").classList.add("no_border");
|
||||
document.getElementById("1_27").classList.add("no_border");
|
||||
document.getElementById("1_28").classList.add("no_border");
|
||||
document.getElementById("2_22").classList.add("no_border");
|
||||
document.getElementById("2_23").classList.add("no_border");
|
||||
document.getElementById("2_24").classList.add("no_border");
|
||||
document.getElementById("2_25").classList.add("no_border");
|
||||
document.getElementById("2_26").classList.add("no_border");
|
||||
document.getElementById("2_27").classList.add("no_border");
|
||||
document.getElementById("2_28").classList.add("no_border");
|
||||
document.getElementById("3_22").classList.add("no_border");
|
||||
document.getElementById("3_23").classList.add("no_border");
|
||||
document.getElementById("3_24").classList.add("no_border");
|
||||
document.getElementById("3_25").classList.add("no_border");
|
||||
document.getElementById("3_26").classList.add("no_border");
|
||||
document.getElementById("3_27").classList.add("no_border");
|
||||
document.getElementById("3_28").classList.add("no_border");
|
||||
document.getElementById("4_22").classList.add("no_border");
|
||||
document.getElementById("4_23").classList.add("no_border");
|
||||
document.getElementById("4_24").classList.add("no_border");
|
||||
document.getElementById("4_25").classList.add("no_border");
|
||||
document.getElementById("4_26").classList.add("no_border");
|
||||
document.getElementById("4_27").classList.add("no_border");
|
||||
document.getElementById("4_28").classList.add("no_border");
|
||||
document.getElementById("5_22").classList.add("no_border");
|
||||
document.getElementById("5_23").classList.add("no_border");
|
||||
document.getElementById("5_24").classList.add("no_border");
|
||||
document.getElementById("5_25").classList.add("no_border");
|
||||
document.getElementById("5_26").classList.add("no_border");
|
||||
document.getElementById("5_27").classList.add("no_border");
|
||||
document.getElementById("5_28").classList.add("no_border");
|
||||
document.getElementById("6_22").classList.add("no_border");
|
||||
document.getElementById("6_23").classList.add("no_border");
|
||||
document.getElementById("6_24").classList.add("no_border");
|
||||
document.getElementById("6_25").classList.add("no_border");
|
||||
document.getElementById("6_26").classList.add("no_border");
|
||||
document.getElementById("6_27").classList.add("no_border");
|
||||
document.getElementById("6_28").classList.add("no_border");
|
||||
document.getElementById("22_0").classList.add("filled");
|
||||
document.getElementById("22_6").classList.add("filled");
|
||||
document.getElementById("23_0").classList.add("filled");
|
||||
document.getElementById("23_6").classList.add("filled");
|
||||
document.getElementById("24_0").classList.add("filled");
|
||||
document.getElementById("24_6").classList.add("filled");
|
||||
document.getElementById("25_0").classList.add("filled");
|
||||
document.getElementById("25_6").classList.add("filled");
|
||||
document.getElementById("26_0").classList.add("filled");
|
||||
document.getElementById("26_6").classList.add("filled");
|
||||
document.getElementById("27_0").classList.add("filled");
|
||||
document.getElementById("27_6").classList.add("filled");
|
||||
document.getElementById("28_0").classList.add("filled");
|
||||
document.getElementById("28_6").classList.add("filled");
|
||||
document.getElementById("22_1").classList.add("filled");
|
||||
document.getElementById("28_1").classList.add("filled");
|
||||
document.getElementById("22_2").classList.add("filled");
|
||||
document.getElementById("28_2").classList.add("filled");
|
||||
document.getElementById("22_3").classList.add("filled");
|
||||
document.getElementById("28_3").classList.add("filled");
|
||||
document.getElementById("22_4").classList.add("filled");
|
||||
document.getElementById("28_4").classList.add("filled");
|
||||
document.getElementById("22_5").classList.add("filled");
|
||||
document.getElementById("28_5").classList.add("filled");
|
||||
document.getElementById("24_2").classList.add("filled");
|
||||
document.getElementById("24_3").classList.add("filled");
|
||||
document.getElementById("24_4").classList.add("filled");
|
||||
document.getElementById("25_2").classList.add("filled");
|
||||
document.getElementById("25_3").classList.add("filled");
|
||||
document.getElementById("25_4").classList.add("filled");
|
||||
document.getElementById("26_2").classList.add("filled");
|
||||
document.getElementById("26_3").classList.add("filled");
|
||||
document.getElementById("26_4").classList.add("filled");
|
||||
document.getElementById("22_0").classList.add("no_border");
|
||||
document.getElementById("22_1").classList.add("no_border");
|
||||
document.getElementById("22_2").classList.add("no_border");
|
||||
document.getElementById("22_3").classList.add("no_border");
|
||||
document.getElementById("22_4").classList.add("no_border");
|
||||
document.getElementById("22_5").classList.add("no_border");
|
||||
document.getElementById("22_6").classList.add("no_border");
|
||||
document.getElementById("23_0").classList.add("no_border");
|
||||
document.getElementById("23_1").classList.add("no_border");
|
||||
document.getElementById("23_2").classList.add("no_border");
|
||||
document.getElementById("23_3").classList.add("no_border");
|
||||
document.getElementById("23_4").classList.add("no_border");
|
||||
document.getElementById("23_5").classList.add("no_border");
|
||||
document.getElementById("23_6").classList.add("no_border");
|
||||
document.getElementById("24_0").classList.add("no_border");
|
||||
document.getElementById("24_1").classList.add("no_border");
|
||||
document.getElementById("24_2").classList.add("no_border");
|
||||
document.getElementById("24_3").classList.add("no_border");
|
||||
document.getElementById("24_4").classList.add("no_border");
|
||||
document.getElementById("24_5").classList.add("no_border");
|
||||
document.getElementById("24_6").classList.add("no_border");
|
||||
document.getElementById("25_0").classList.add("no_border");
|
||||
document.getElementById("25_1").classList.add("no_border");
|
||||
document.getElementById("25_2").classList.add("no_border");
|
||||
document.getElementById("25_3").classList.add("no_border");
|
||||
document.getElementById("25_4").classList.add("no_border");
|
||||
document.getElementById("25_5").classList.add("no_border");
|
||||
document.getElementById("25_6").classList.add("no_border");
|
||||
document.getElementById("26_0").classList.add("no_border");
|
||||
document.getElementById("26_1").classList.add("no_border");
|
||||
document.getElementById("26_2").classList.add("no_border");
|
||||
document.getElementById("26_3").classList.add("no_border");
|
||||
document.getElementById("26_4").classList.add("no_border");
|
||||
document.getElementById("26_5").classList.add("no_border");
|
||||
document.getElementById("26_6").classList.add("no_border");
|
||||
document.getElementById("27_0").classList.add("no_border");
|
||||
document.getElementById("27_1").classList.add("no_border");
|
||||
document.getElementById("27_2").classList.add("no_border");
|
||||
document.getElementById("27_3").classList.add("no_border");
|
||||
document.getElementById("27_4").classList.add("no_border");
|
||||
document.getElementById("27_5").classList.add("no_border");
|
||||
document.getElementById("27_6").classList.add("no_border");
|
||||
document.getElementById("28_0").classList.add("no_border");
|
||||
document.getElementById("28_1").classList.add("no_border");
|
||||
document.getElementById("28_2").classList.add("no_border");
|
||||
document.getElementById("28_3").classList.add("no_border");
|
||||
document.getElementById("28_4").classList.add("no_border");
|
||||
document.getElementById("28_5").classList.add("no_border");
|
||||
document.getElementById("28_6").classList.add("no_border");
|
||||
document.getElementById("22_22").classList.add("filled");
|
||||
document.getElementById("20_20").classList.add("filled");
|
||||
document.getElementById("20_24").classList.add("filled");
|
||||
document.getElementById("21_20").classList.add("filled");
|
||||
document.getElementById("21_24").classList.add("filled");
|
||||
document.getElementById("22_20").classList.add("filled");
|
||||
document.getElementById("22_24").classList.add("filled");
|
||||
document.getElementById("23_20").classList.add("filled");
|
||||
document.getElementById("23_24").classList.add("filled");
|
||||
document.getElementById("24_20").classList.add("filled");
|
||||
document.getElementById("24_24").classList.add("filled");
|
||||
document.getElementById("20_21").classList.add("filled");
|
||||
document.getElementById("24_21").classList.add("filled");
|
||||
document.getElementById("20_22").classList.add("filled");
|
||||
document.getElementById("24_22").classList.add("filled");
|
||||
document.getElementById("20_23").classList.add("filled");
|
||||
document.getElementById("24_23").classList.add("filled");
|
||||
document.getElementById("20_20").classList.add("no_border");
|
||||
document.getElementById("20_21").classList.add("no_border");
|
||||
document.getElementById("20_22").classList.add("no_border");
|
||||
document.getElementById("20_23").classList.add("no_border");
|
||||
document.getElementById("20_24").classList.add("no_border");
|
||||
document.getElementById("21_20").classList.add("no_border");
|
||||
document.getElementById("21_21").classList.add("no_border");
|
||||
document.getElementById("21_22").classList.add("no_border");
|
||||
document.getElementById("21_23").classList.add("no_border");
|
||||
document.getElementById("21_24").classList.add("no_border");
|
||||
document.getElementById("22_20").classList.add("no_border");
|
||||
document.getElementById("22_21").classList.add("no_border");
|
||||
document.getElementById("22_22").classList.add("no_border");
|
||||
document.getElementById("22_23").classList.add("no_border");
|
||||
document.getElementById("22_24").classList.add("no_border");
|
||||
document.getElementById("23_20").classList.add("no_border");
|
||||
document.getElementById("23_21").classList.add("no_border");
|
||||
document.getElementById("23_22").classList.add("no_border");
|
||||
document.getElementById("23_23").classList.add("no_border");
|
||||
document.getElementById("23_24").classList.add("no_border");
|
||||
document.getElementById("24_20").classList.add("no_border");
|
||||
document.getElementById("24_21").classList.add("no_border");
|
||||
document.getElementById("24_22").classList.add("no_border");
|
||||
document.getElementById("24_23").classList.add("no_border");
|
||||
document.getElementById("24_24").classList.add("no_border");
|
||||
document.getElementById("0_4").classList.add("col_block_divider");
|
||||
document.getElementById("0_9").classList.add("col_block_divider");
|
||||
document.getElementById("0_14").classList.add("col_block_divider");
|
||||
document.getElementById("0_19").classList.add("col_block_divider");
|
||||
document.getElementById("0_24").classList.add("col_block_divider");
|
||||
document.getElementById("1_4").classList.add("col_block_divider");
|
||||
document.getElementById("1_9").classList.add("col_block_divider");
|
||||
document.getElementById("1_14").classList.add("col_block_divider");
|
||||
document.getElementById("1_19").classList.add("col_block_divider");
|
||||
document.getElementById("1_24").classList.add("col_block_divider");
|
||||
document.getElementById("2_4").classList.add("col_block_divider");
|
||||
document.getElementById("2_9").classList.add("col_block_divider");
|
||||
document.getElementById("2_14").classList.add("col_block_divider");
|
||||
document.getElementById("2_19").classList.add("col_block_divider");
|
||||
document.getElementById("2_24").classList.add("col_block_divider");
|
||||
document.getElementById("3_4").classList.add("col_block_divider");
|
||||
document.getElementById("3_9").classList.add("col_block_divider");
|
||||
document.getElementById("3_14").classList.add("col_block_divider");
|
||||
document.getElementById("3_19").classList.add("col_block_divider");
|
||||
document.getElementById("3_24").classList.add("col_block_divider");
|
||||
document.getElementById("4_4").classList.add("col_block_divider");
|
||||
document.getElementById("4_9").classList.add("col_block_divider");
|
||||
document.getElementById("4_14").classList.add("col_block_divider");
|
||||
document.getElementById("4_19").classList.add("col_block_divider");
|
||||
document.getElementById("4_24").classList.add("col_block_divider");
|
||||
document.getElementById("5_4").classList.add("col_block_divider");
|
||||
document.getElementById("5_9").classList.add("col_block_divider");
|
||||
document.getElementById("5_14").classList.add("col_block_divider");
|
||||
document.getElementById("5_19").classList.add("col_block_divider");
|
||||
document.getElementById("5_24").classList.add("col_block_divider");
|
||||
document.getElementById("6_4").classList.add("col_block_divider");
|
||||
document.getElementById("6_9").classList.add("col_block_divider");
|
||||
document.getElementById("6_14").classList.add("col_block_divider");
|
||||
document.getElementById("6_19").classList.add("col_block_divider");
|
||||
document.getElementById("6_24").classList.add("col_block_divider");
|
||||
document.getElementById("7_4").classList.add("col_block_divider");
|
||||
document.getElementById("7_9").classList.add("col_block_divider");
|
||||
document.getElementById("7_14").classList.add("col_block_divider");
|
||||
document.getElementById("7_19").classList.add("col_block_divider");
|
||||
document.getElementById("7_24").classList.add("col_block_divider");
|
||||
document.getElementById("8_4").classList.add("col_block_divider");
|
||||
document.getElementById("8_9").classList.add("col_block_divider");
|
||||
document.getElementById("8_14").classList.add("col_block_divider");
|
||||
document.getElementById("8_19").classList.add("col_block_divider");
|
||||
document.getElementById("8_24").classList.add("col_block_divider");
|
||||
document.getElementById("9_4").classList.add("col_block_divider");
|
||||
document.getElementById("9_9").classList.add("col_block_divider");
|
||||
document.getElementById("9_14").classList.add("col_block_divider");
|
||||
document.getElementById("9_19").classList.add("col_block_divider");
|
||||
document.getElementById("9_24").classList.add("col_block_divider");
|
||||
document.getElementById("10_4").classList.add("col_block_divider");
|
||||
document.getElementById("10_9").classList.add("col_block_divider");
|
||||
document.getElementById("10_14").classList.add("col_block_divider");
|
||||
document.getElementById("10_19").classList.add("col_block_divider");
|
||||
document.getElementById("10_24").classList.add("col_block_divider");
|
||||
document.getElementById("11_4").classList.add("col_block_divider");
|
||||
document.getElementById("11_9").classList.add("col_block_divider");
|
||||
document.getElementById("11_14").classList.add("col_block_divider");
|
||||
document.getElementById("11_19").classList.add("col_block_divider");
|
||||
document.getElementById("11_24").classList.add("col_block_divider");
|
||||
document.getElementById("12_4").classList.add("col_block_divider");
|
||||
document.getElementById("12_9").classList.add("col_block_divider");
|
||||
document.getElementById("12_14").classList.add("col_block_divider");
|
||||
document.getElementById("12_19").classList.add("col_block_divider");
|
||||
document.getElementById("12_24").classList.add("col_block_divider");
|
||||
document.getElementById("13_4").classList.add("col_block_divider");
|
||||
document.getElementById("13_9").classList.add("col_block_divider");
|
||||
document.getElementById("13_14").classList.add("col_block_divider");
|
||||
document.getElementById("13_19").classList.add("col_block_divider");
|
||||
document.getElementById("13_24").classList.add("col_block_divider");
|
||||
document.getElementById("14_4").classList.add("col_block_divider");
|
||||
document.getElementById("14_9").classList.add("col_block_divider");
|
||||
document.getElementById("14_14").classList.add("col_block_divider");
|
||||
document.getElementById("14_19").classList.add("col_block_divider");
|
||||
document.getElementById("14_24").classList.add("col_block_divider");
|
||||
document.getElementById("15_4").classList.add("col_block_divider");
|
||||
document.getElementById("15_9").classList.add("col_block_divider");
|
||||
document.getElementById("15_14").classList.add("col_block_divider");
|
||||
document.getElementById("15_19").classList.add("col_block_divider");
|
||||
document.getElementById("15_24").classList.add("col_block_divider");
|
||||
document.getElementById("16_4").classList.add("col_block_divider");
|
||||
document.getElementById("16_9").classList.add("col_block_divider");
|
||||
document.getElementById("16_14").classList.add("col_block_divider");
|
||||
document.getElementById("16_19").classList.add("col_block_divider");
|
||||
document.getElementById("16_24").classList.add("col_block_divider");
|
||||
document.getElementById("17_4").classList.add("col_block_divider");
|
||||
document.getElementById("17_9").classList.add("col_block_divider");
|
||||
document.getElementById("17_14").classList.add("col_block_divider");
|
||||
document.getElementById("17_19").classList.add("col_block_divider");
|
||||
document.getElementById("17_24").classList.add("col_block_divider");
|
||||
document.getElementById("18_4").classList.add("col_block_divider");
|
||||
document.getElementById("18_9").classList.add("col_block_divider");
|
||||
document.getElementById("18_14").classList.add("col_block_divider");
|
||||
document.getElementById("18_19").classList.add("col_block_divider");
|
||||
document.getElementById("18_24").classList.add("col_block_divider");
|
||||
document.getElementById("19_4").classList.add("col_block_divider");
|
||||
document.getElementById("19_9").classList.add("col_block_divider");
|
||||
document.getElementById("19_14").classList.add("col_block_divider");
|
||||
document.getElementById("19_19").classList.add("col_block_divider");
|
||||
document.getElementById("19_24").classList.add("col_block_divider");
|
||||
document.getElementById("20_4").classList.add("col_block_divider");
|
||||
document.getElementById("20_9").classList.add("col_block_divider");
|
||||
document.getElementById("20_14").classList.add("col_block_divider");
|
||||
document.getElementById("20_19").classList.add("col_block_divider");
|
||||
document.getElementById("20_24").classList.add("col_block_divider");
|
||||
document.getElementById("21_4").classList.add("col_block_divider");
|
||||
document.getElementById("21_9").classList.add("col_block_divider");
|
||||
document.getElementById("21_14").classList.add("col_block_divider");
|
||||
document.getElementById("21_19").classList.add("col_block_divider");
|
||||
document.getElementById("21_24").classList.add("col_block_divider");
|
||||
document.getElementById("22_4").classList.add("col_block_divider");
|
||||
document.getElementById("22_9").classList.add("col_block_divider");
|
||||
document.getElementById("22_14").classList.add("col_block_divider");
|
||||
document.getElementById("22_19").classList.add("col_block_divider");
|
||||
document.getElementById("22_24").classList.add("col_block_divider");
|
||||
document.getElementById("23_4").classList.add("col_block_divider");
|
||||
document.getElementById("23_9").classList.add("col_block_divider");
|
||||
document.getElementById("23_14").classList.add("col_block_divider");
|
||||
document.getElementById("23_19").classList.add("col_block_divider");
|
||||
document.getElementById("23_24").classList.add("col_block_divider");
|
||||
document.getElementById("24_4").classList.add("col_block_divider");
|
||||
document.getElementById("24_9").classList.add("col_block_divider");
|
||||
document.getElementById("24_14").classList.add("col_block_divider");
|
||||
document.getElementById("24_19").classList.add("col_block_divider");
|
||||
document.getElementById("24_24").classList.add("col_block_divider");
|
||||
document.getElementById("25_4").classList.add("col_block_divider");
|
||||
document.getElementById("25_9").classList.add("col_block_divider");
|
||||
document.getElementById("25_14").classList.add("col_block_divider");
|
||||
document.getElementById("25_19").classList.add("col_block_divider");
|
||||
document.getElementById("25_24").classList.add("col_block_divider");
|
||||
document.getElementById("26_4").classList.add("col_block_divider");
|
||||
document.getElementById("26_9").classList.add("col_block_divider");
|
||||
document.getElementById("26_14").classList.add("col_block_divider");
|
||||
document.getElementById("26_19").classList.add("col_block_divider");
|
||||
document.getElementById("26_24").classList.add("col_block_divider");
|
||||
document.getElementById("27_4").classList.add("col_block_divider");
|
||||
document.getElementById("27_9").classList.add("col_block_divider");
|
||||
document.getElementById("27_14").classList.add("col_block_divider");
|
||||
document.getElementById("27_19").classList.add("col_block_divider");
|
||||
document.getElementById("27_24").classList.add("col_block_divider");
|
||||
document.getElementById("28_4").classList.add("col_block_divider");
|
||||
document.getElementById("28_9").classList.add("col_block_divider");
|
||||
document.getElementById("28_14").classList.add("col_block_divider");
|
||||
document.getElementById("28_19").classList.add("col_block_divider");
|
||||
document.getElementById("28_24").classList.add("col_block_divider");
|
||||
document.getElementById("4_0").classList.add("row_block_divider");
|
||||
document.getElementById("4_1").classList.add("row_block_divider");
|
||||
document.getElementById("4_2").classList.add("row_block_divider");
|
||||
document.getElementById("4_3").classList.add("row_block_divider");
|
||||
document.getElementById("4_4").classList.add("row_block_divider");
|
||||
document.getElementById("4_5").classList.add("row_block_divider");
|
||||
document.getElementById("4_6").classList.add("row_block_divider");
|
||||
document.getElementById("4_7").classList.add("row_block_divider");
|
||||
document.getElementById("4_8").classList.add("row_block_divider");
|
||||
document.getElementById("4_9").classList.add("row_block_divider");
|
||||
document.getElementById("4_10").classList.add("row_block_divider");
|
||||
document.getElementById("4_11").classList.add("row_block_divider");
|
||||
document.getElementById("4_12").classList.add("row_block_divider");
|
||||
document.getElementById("4_13").classList.add("row_block_divider");
|
||||
document.getElementById("4_14").classList.add("row_block_divider");
|
||||
document.getElementById("4_15").classList.add("row_block_divider");
|
||||
document.getElementById("4_16").classList.add("row_block_divider");
|
||||
document.getElementById("4_17").classList.add("row_block_divider");
|
||||
document.getElementById("4_18").classList.add("row_block_divider");
|
||||
document.getElementById("4_19").classList.add("row_block_divider");
|
||||
document.getElementById("4_20").classList.add("row_block_divider");
|
||||
document.getElementById("4_21").classList.add("row_block_divider");
|
||||
document.getElementById("4_22").classList.add("row_block_divider");
|
||||
document.getElementById("4_23").classList.add("row_block_divider");
|
||||
document.getElementById("4_24").classList.add("row_block_divider");
|
||||
document.getElementById("4_25").classList.add("row_block_divider");
|
||||
document.getElementById("4_26").classList.add("row_block_divider");
|
||||
document.getElementById("4_27").classList.add("row_block_divider");
|
||||
document.getElementById("4_28").classList.add("row_block_divider");
|
||||
document.getElementById("9_0").classList.add("row_block_divider");
|
||||
document.getElementById("9_1").classList.add("row_block_divider");
|
||||
document.getElementById("9_2").classList.add("row_block_divider");
|
||||
document.getElementById("9_3").classList.add("row_block_divider");
|
||||
document.getElementById("9_4").classList.add("row_block_divider");
|
||||
document.getElementById("9_5").classList.add("row_block_divider");
|
||||
document.getElementById("9_6").classList.add("row_block_divider");
|
||||
document.getElementById("9_7").classList.add("row_block_divider");
|
||||
document.getElementById("9_8").classList.add("row_block_divider");
|
||||
document.getElementById("9_9").classList.add("row_block_divider");
|
||||
document.getElementById("9_10").classList.add("row_block_divider");
|
||||
document.getElementById("9_11").classList.add("row_block_divider");
|
||||
document.getElementById("9_12").classList.add("row_block_divider");
|
||||
document.getElementById("9_13").classList.add("row_block_divider");
|
||||
document.getElementById("9_14").classList.add("row_block_divider");
|
||||
document.getElementById("9_15").classList.add("row_block_divider");
|
||||
document.getElementById("9_16").classList.add("row_block_divider");
|
||||
document.getElementById("9_17").classList.add("row_block_divider");
|
||||
document.getElementById("9_18").classList.add("row_block_divider");
|
||||
document.getElementById("9_19").classList.add("row_block_divider");
|
||||
document.getElementById("9_20").classList.add("row_block_divider");
|
||||
document.getElementById("9_21").classList.add("row_block_divider");
|
||||
document.getElementById("9_22").classList.add("row_block_divider");
|
||||
document.getElementById("9_23").classList.add("row_block_divider");
|
||||
document.getElementById("9_24").classList.add("row_block_divider");
|
||||
document.getElementById("9_25").classList.add("row_block_divider");
|
||||
document.getElementById("9_26").classList.add("row_block_divider");
|
||||
document.getElementById("9_27").classList.add("row_block_divider");
|
||||
document.getElementById("9_28").classList.add("row_block_divider");
|
||||
document.getElementById("14_0").classList.add("row_block_divider");
|
||||
document.getElementById("14_1").classList.add("row_block_divider");
|
||||
document.getElementById("14_2").classList.add("row_block_divider");
|
||||
document.getElementById("14_3").classList.add("row_block_divider");
|
||||
document.getElementById("14_4").classList.add("row_block_divider");
|
||||
document.getElementById("14_5").classList.add("row_block_divider");
|
||||
document.getElementById("14_6").classList.add("row_block_divider");
|
||||
document.getElementById("14_7").classList.add("row_block_divider");
|
||||
document.getElementById("14_8").classList.add("row_block_divider");
|
||||
document.getElementById("14_9").classList.add("row_block_divider");
|
||||
document.getElementById("14_10").classList.add("row_block_divider");
|
||||
document.getElementById("14_11").classList.add("row_block_divider");
|
||||
document.getElementById("14_12").classList.add("row_block_divider");
|
||||
document.getElementById("14_13").classList.add("row_block_divider");
|
||||
document.getElementById("14_14").classList.add("row_block_divider");
|
||||
document.getElementById("14_15").classList.add("row_block_divider");
|
||||
document.getElementById("14_16").classList.add("row_block_divider");
|
||||
document.getElementById("14_17").classList.add("row_block_divider");
|
||||
document.getElementById("14_18").classList.add("row_block_divider");
|
||||
document.getElementById("14_19").classList.add("row_block_divider");
|
||||
document.getElementById("14_20").classList.add("row_block_divider");
|
||||
document.getElementById("14_21").classList.add("row_block_divider");
|
||||
document.getElementById("14_22").classList.add("row_block_divider");
|
||||
document.getElementById("14_23").classList.add("row_block_divider");
|
||||
document.getElementById("14_24").classList.add("row_block_divider");
|
||||
document.getElementById("14_25").classList.add("row_block_divider");
|
||||
document.getElementById("14_26").classList.add("row_block_divider");
|
||||
document.getElementById("14_27").classList.add("row_block_divider");
|
||||
document.getElementById("14_28").classList.add("row_block_divider");
|
||||
document.getElementById("19_0").classList.add("row_block_divider");
|
||||
document.getElementById("19_1").classList.add("row_block_divider");
|
||||
document.getElementById("19_2").classList.add("row_block_divider");
|
||||
document.getElementById("19_3").classList.add("row_block_divider");
|
||||
document.getElementById("19_4").classList.add("row_block_divider");
|
||||
document.getElementById("19_5").classList.add("row_block_divider");
|
||||
document.getElementById("19_6").classList.add("row_block_divider");
|
||||
document.getElementById("19_7").classList.add("row_block_divider");
|
||||
document.getElementById("19_8").classList.add("row_block_divider");
|
||||
document.getElementById("19_9").classList.add("row_block_divider");
|
||||
document.getElementById("19_10").classList.add("row_block_divider");
|
||||
document.getElementById("19_11").classList.add("row_block_divider");
|
||||
document.getElementById("19_12").classList.add("row_block_divider");
|
||||
document.getElementById("19_13").classList.add("row_block_divider");
|
||||
document.getElementById("19_14").classList.add("row_block_divider");
|
||||
document.getElementById("19_15").classList.add("row_block_divider");
|
||||
document.getElementById("19_16").classList.add("row_block_divider");
|
||||
document.getElementById("19_17").classList.add("row_block_divider");
|
||||
document.getElementById("19_18").classList.add("row_block_divider");
|
||||
document.getElementById("19_19").classList.add("row_block_divider");
|
||||
document.getElementById("19_20").classList.add("row_block_divider");
|
||||
document.getElementById("19_21").classList.add("row_block_divider");
|
||||
document.getElementById("19_22").classList.add("row_block_divider");
|
||||
document.getElementById("19_23").classList.add("row_block_divider");
|
||||
document.getElementById("19_24").classList.add("row_block_divider");
|
||||
document.getElementById("19_25").classList.add("row_block_divider");
|
||||
document.getElementById("19_26").classList.add("row_block_divider");
|
||||
document.getElementById("19_27").classList.add("row_block_divider");
|
||||
document.getElementById("19_28").classList.add("row_block_divider");
|
||||
document.getElementById("24_0").classList.add("row_block_divider");
|
||||
document.getElementById("24_1").classList.add("row_block_divider");
|
||||
document.getElementById("24_2").classList.add("row_block_divider");
|
||||
document.getElementById("24_3").classList.add("row_block_divider");
|
||||
document.getElementById("24_4").classList.add("row_block_divider");
|
||||
document.getElementById("24_5").classList.add("row_block_divider");
|
||||
document.getElementById("24_6").classList.add("row_block_divider");
|
||||
document.getElementById("24_7").classList.add("row_block_divider");
|
||||
document.getElementById("24_8").classList.add("row_block_divider");
|
||||
document.getElementById("24_9").classList.add("row_block_divider");
|
||||
document.getElementById("24_10").classList.add("row_block_divider");
|
||||
document.getElementById("24_11").classList.add("row_block_divider");
|
||||
document.getElementById("24_12").classList.add("row_block_divider");
|
||||
document.getElementById("24_13").classList.add("row_block_divider");
|
||||
document.getElementById("24_14").classList.add("row_block_divider");
|
||||
document.getElementById("24_15").classList.add("row_block_divider");
|
||||
document.getElementById("24_16").classList.add("row_block_divider");
|
||||
document.getElementById("24_17").classList.add("row_block_divider");
|
||||
document.getElementById("24_18").classList.add("row_block_divider");
|
||||
document.getElementById("24_19").classList.add("row_block_divider");
|
||||
document.getElementById("24_20").classList.add("row_block_divider");
|
||||
document.getElementById("24_21").classList.add("row_block_divider");
|
||||
document.getElementById("24_22").classList.add("row_block_divider");
|
||||
document.getElementById("24_23").classList.add("row_block_divider");
|
||||
document.getElementById("24_24").classList.add("row_block_divider");
|
||||
document.getElementById("24_25").classList.add("row_block_divider");
|
||||
document.getElementById("24_26").classList.add("row_block_divider");
|
||||
document.getElementById("24_27").classList.add("row_block_divider");
|
||||
document.getElementById("24_28").classList.add("row_block_divider");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,10 +14,8 @@ Static QR Formats:
|
||||
- PSBT
|
||||
- Base64 (if the PSBT byte size is too large, SS may have trouble scanning)
|
||||
- Seed
|
||||
- [SeedSigner SeedQR](seed_qr/README.md) format
|
||||
- A 48 or 96 length string of numbers representing a bip39 wordlist (all wordlist languages supported). The numeric sequence is a concatenation of four-digit, zero-padded segments. Each four-digit segment represents a bip39 word expressed by a zero-indexed position in the wordlist. For example "0000" is abandon in the english bip39 wordlist.
|
||||
- [SeedSigner CompactSeedQR](seed_qr/README.md) format
|
||||
- The 128- or 256-bit entropy encoded as a binary QR.
|
||||
- SeedSigner Seed QR format
|
||||
- A 48 or 96 length string of numbers representing a bip39 wordlist (all wordlist languages supported). The numeric sequence is a concatination of four-digit zero padded segments. Each four-digit segment represents a bip39 word expressed by a zero-indexed position in the wordlist. For example "0000" is abandon in the english bip39 wordlist.
|
||||
- English Bip39 Mnemonic words separated by a space. Currently only supports 12 and 24 word seeds.
|
||||
- English Bip39 Mnemonic with only first 4 letters seperated by a space. Currently only supports 12 and 24 word seeds.
|
||||
|
||||
|
||||
@@ -1,482 +0,0 @@
|
||||
# SeedQR Format Specification
|
||||
|
||||
[SeedSigner](https://github.com/SeedSigner/seedsigner/) is an open source, DIY, fully-airgapped Bitcoin hardware wallet that wipes all private data from memory each time it's turned off. That means users need to re-enter their Bitcoin private key each time they use it.
|
||||
|
||||
To speed up this key entry process we have defined a way to encode a private key as a QR code that can be instantly scanned into a SeedSigner or potentially any other Bitcoin hardware wallet that has a camera.
|
||||
|
||||
The approach is specifically designed to encode the minimum possible amount of data in order to keep the resulting QR code small enough that it can be transcribed *by hand*. This sounds ridiculous at first, but remember that this is secret data that should never be stored in any digital medium. And even printers present some additional risk vectors.
|
||||
|
||||
<table align="center">
|
||||
<tr><td><img src="img/handmade_qr.jpg"><br/><em>Obviously this SeedQR is just for demonstration purposes; never photograph your SeedQRs!</em></td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
We have defined two QR formats:
|
||||
* Standard SeedQR
|
||||
* And a more advanced CompactSeedQR
|
||||
|
||||
Specifications for each follow below, as well as discussion of the pros and cons of each format.
|
||||
|
||||
|
||||
|
||||
## Quick Review of BIP-39 Mnemonic Seed Phrases
|
||||
The typical method for backing up a Bitcoin private key is to store it as a [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic seed phrase that consists of 12 or 24 words.
|
||||
|
||||
Each word comes from a [list of 2048 words](https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt). The words themselves are meaningless; all that matters is the word's position number (aka index) in the word list.
|
||||
|
||||
For example, "tomato" is the 1,825th word in the list.
|
||||
|
||||
But code always starts counting list items with zero. So the index of "tomato" is actually `1824` (if you're looking at the github wordlist line numbers, just remember to always subtract one).
|
||||
|
||||
So we can transcode a 12-word seed into a series of indices:
|
||||
|
||||
```
|
||||
1. vacuum 1924
|
||||
2. bridge 222
|
||||
3. buddy 235
|
||||
4. supreme 1743
|
||||
5. exclude 631
|
||||
6. milk 1124
|
||||
7. consider 378
|
||||
8. tail 1770
|
||||
9. expand 641
|
||||
10. wasp 1980
|
||||
11. pattern 1290
|
||||
12. nuclear 1210
|
||||
```
|
||||
|
||||
# "Standard" `SeedQR` Specification
|
||||
We take the indices of the mnemonic seed phrase and concatenate them into one long stream of digits. Each index must be exactly four digits so shorter numbers must be zero-padded (`12` becomes `0012`).
|
||||
|
||||
Using the example above:
|
||||
|
||||
```bash
|
||||
# with zero-padded indices:
|
||||
1924 0222 0235 1743 0631 1124 0378 1770 0641 1980 1290 1210
|
||||
|
||||
# final assembled digit stream:
|
||||
192402220235174306311124037817700641198012901210
|
||||
```
|
||||
|
||||
This digit stream is then encoded into a QR code.
|
||||
|
||||
|
||||
### QR Code Data Formats
|
||||
It's important to note here that QR codes can encode data in a number of different ways:
|
||||
|
||||
<table align="center">
|
||||
<tr><td align="center"><img src="img/qrcode_capacity.png"><br/><em><a href="https://www.qrcode.com/en/about/version.html">qrcode.com</a></em></td></tr>
|
||||
</table>
|
||||
|
||||
QR codes are typically used to encode a website url, in which case the "Alphanumeric" format has to be used (the encoded data can consist of upper- and lowercase letters, numbers, and certain allowed symbols).
|
||||
|
||||
If you have a long url like `https://ohnoihavealongurl.com` (29 characters), the chart shows that it would not fit in a 21x21 QR code; its max capacity is 25 alphanumeric chars. But it's within the 47-char capacity of the 29x29 size.
|
||||
|
||||
### Bit efficiency matters
|
||||
Notice that the "Numeric" column has greater capacity. This is because when you have fewer possible characters to encode, it takes less data to specify each one.
|
||||
|
||||
Alphanumeric format supports 44 possible characters. They are encoded as pairs, so that means there are 44^2 = 1,936 possible pairings to describe. Each pair can then be encoded using 11 bits:
|
||||
|
||||
```
|
||||
2^11 = 2048
|
||||
```
|
||||
|
||||
If you only used 10 bits, you could only specify 2^10 = 1,024 possible values; not nearly enough for the 1,936 possible pairings. 11 bits is the best this format can do.
|
||||
|
||||
The Numeric format is able to store 3 digits using just 10 bits. The largest possible 3-digit number is `999`, therefore 10 bits (2^10 = 1,024) is sufficient.
|
||||
|
||||
This is why a 25x25 QR code can only store 47 alphanumeric characters vs 77 numeric digits. It's the same amount of total data in terms of bits:
|
||||
|
||||
```
|
||||
47 alphanumeric chars / 2 chars per pair * 11 bits per pair = 259 bits
|
||||
|
||||
77 numeric digits / 3 digits per group * 10 bits per group = 257 bits
|
||||
```
|
||||
*(both formats round up to 260 bits)*
|
||||
|
||||
Armed with this knowledge, we're ready to encode our SeedQR.
|
||||
|
||||
|
||||
## Generate the SeedQR
|
||||
Our SeedQR data will consist solely of numeric digits so that we can use the more efficient "Numeric" format. Our 12-word mnemonic seed phrase requires:
|
||||
|
||||
```
|
||||
12 words * 4 digits per word = 48 digits
|
||||
```
|
||||
|
||||
48 numeric digits won't fit in a 21x21 using the "L" (Low) error correction mode (max Numeric capacity is 41) but will easily fit in a 25x25:
|
||||
|
||||
<table align="center">
|
||||
<tr><td><img src="img/standard_12word.png"></td></tr>
|
||||
</table>
|
||||
|
||||
If you scan this QR code with your phone, you'll see the human-readable digit stream:
|
||||
|
||||
<table align="center">
|
||||
<tr><td><img src="img/phone_screenshot_standard.jpg"></td></tr>
|
||||
</table>
|
||||
|
||||
And see for yourself that the digit stream matches what was generated above from the mnemonic seed word indices.
|
||||
|
||||
```
|
||||
192402220235174306311124037817700641198012901210
|
||||
```
|
||||
|
||||
At this point it is trivial for a SeedSigner or other Bitcoin hardware wallets to parse the digit stream back into the individual BIP-39 wordlist indices and rebuild your entire mnemonic seed phrase.
|
||||
|
||||
Looking back at the QR code capacity chart for Numeric data, we know exactly how large the resulting SeedQRs will be:
|
||||
```
|
||||
12-word mnemonic (48 digits) = 25x25
|
||||
24-word mnemonic (96 digits) = 29x29
|
||||
```
|
||||
|
||||
Examples:
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/standard_12word.png"><br/>12-word Standard SeedQR</td>
|
||||
<td align="center"><img src="img/vector1_standard_24word.png"><br/>24-word Standard SeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
# `CompactSeedQR` Specification
|
||||
The `CompactSeedQR` format builds upon the "Standard" `SeedQR` format by further optimizing how the data is stored in order to generate smaller QR codes that are easier to transcribe by hand.
|
||||
|
||||
Notice in the QR code capacity chart above that there's also a "Binary" column, but its relative capacity count initially looks pretty unimpressive; a 25x25 QR code is limited to 32.
|
||||
|
||||
But here the unit being described isn't alphanumeric characters or numeric digits; it's max number of bytes.
|
||||
|
||||
```
|
||||
1 byte = 8 bits
|
||||
```
|
||||
|
||||
Rather than having the QR format interpret our data as numbers or characters, we can directly encode the relevant bits that determine our Bitcoin private key.
|
||||
|
||||
We can extract exactly those bits from our mnemonic seed phrase digit stream that we generated above.
|
||||
|
||||
Let's bring back our mnemonic seed phrase's individual indices:
|
||||
|
||||
```bash
|
||||
1924 0222 0235 1743 0631 1124 0378 1770 0641 1980 1290 1210
|
||||
```
|
||||
|
||||
We know that the BIP-39 wordlist contains exactly 2048 words. That means we will need 11 bits to express each index in binary (2^11 = 2048; not a coincidence!):
|
||||
```
|
||||
1924 11110000100
|
||||
0222 00011011110
|
||||
0235 00011101011
|
||||
1743 11011001111
|
||||
0631 01001110111
|
||||
1124 10001100100
|
||||
0378 00101111010
|
||||
1770 11011101010
|
||||
0641 01010000001
|
||||
1980 11110111100
|
||||
1290 10100001010
|
||||
1210 10010111010
|
||||
```
|
||||
|
||||
We concatenate all those binary values to one large 132-bit stream:
|
||||
```
|
||||
111100001000001101111000011101011110110011110100111011110001100100001011110101101110101001010000001111101111001010000101010010111010
|
||||
```
|
||||
|
||||
BIP-39 specifies a checksum word at the end of each mnemonic seed phrase. In a 12-word mnemonic, the last 4 bits are the checksum. In a 24-word mnemonic, the last 8 bits are the checksum.
|
||||
|
||||
The checksum is trivially calculated from the prior bits (in this case, the first 128 bits of our stream). Therefore we do not need to include those bits in our CompactSeedQR.
|
||||
|
||||
```
|
||||
12-word mnemonic = 12 words * 11 bits per word = 132 bits
|
||||
12-word CompactSeedQR = 132 bits - 4 checksum bits = 128 bits
|
||||
|
||||
24-word mnemonic = 24 words * 11 bits per word = 264 bits
|
||||
24-word CompactSeedQR = 264 bits - 8 checksum bits = 256 bits
|
||||
```
|
||||
|
||||
How well will these bit streams fit in a "Binary" QR code? Referring back to the QR code capacity chart we find the following for the "L" (Low) error correction mode:
|
||||
```
|
||||
12-word mnemonic: 128bits / 8 bits per byte = 16 bytes = 21x21
|
||||
|
||||
24-word mnemonic: 256bits / 8 bits per byte = 32 bytes = 25x25
|
||||
```
|
||||
|
||||
So by using the optimally-efficient Binary encoding, we have made each CompactSeedQR one size smaller than its Standard SeedQR counterpart.
|
||||
|
||||
Examples:
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector4_compact_12word.png"><br/>12-word CompactSeedQR</td>
|
||||
<td align="center"><img src="img/vector1_compact_24word.png"><br/>24-word CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
# Standard SeedQR vs CompactSeedQR
|
||||
|
||||
Here are the two formats side-by-side.
|
||||
|
||||
The same 12-word seed in each format:
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector4_standard_12word.png"><br/>Standard SeedQR (25x25)</td>
|
||||
<td align="center"><img src="img/vector4_compact_12word.png"><br/>CompactSeedQR (21x21)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The same 24-word seed in each format:
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector1_standard_24word.png"><br/>Standard SeedQR (29x29)</td>
|
||||
<td align="center"><img src="img/vector1_compact_24word.png"><br/>CompactSeedQR (25x25)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Since the goal of the SeedQR concept is to make it easy to transcribe by hand, it's natural to assume that the CompactSeedQR is better.
|
||||
|
||||
Let's compare actual "real estate" in each size. All of our QR sizes have three large 8x8 registration blocks in the corners. 29x29 and 25x25 have a smaller 5x5 registration block in the lower right.
|
||||
|
||||
```
|
||||
29x29 - (3*8x8) - 5x5 = 624 blocks
|
||||
25x25 - (3*8x8) - 5x5 = 408 blocks
|
||||
21x21 - (3*8x8) = 249 blocks
|
||||
```
|
||||
|
||||
So by shrinking a 24-word Standard SeedQR from 29x29 down to a CompactSeedQR at 25x25, we've reduced the area we have to manually transcribe to:
|
||||
|
||||
```
|
||||
408 / 624 = 65%
|
||||
```
|
||||
|
||||
And similarly, shrinking a 12-word 25x25 Standard SeedQR down to a CompactSeedQR at 21x21:
|
||||
|
||||
```
|
||||
249 / 408 = 61%
|
||||
```
|
||||
|
||||
If you're manually punching holes into a metal SeedQR plate, that's cutting out about 35-40% of the work!
|
||||
|
||||
<table align="center">
|
||||
<tr><td><img src="img/seedqr_plate.jpg"></td></tr>
|
||||
</table>
|
||||
|
||||
But there are other tradeoffs to consider.
|
||||
|
||||
## Recoverability
|
||||
If you lose your SeedSigner or somehow the project is abandoned or banned, how will you read back your SeedQR?
|
||||
|
||||
With the Standard SeedQR format this is trivial--any smartphone can decode the numeric digit stream. But the CompactSeedQR's raw byte data is not decipherable in the same way. Most QR readers today assume the data is either alphanumeric or human-readable numeric data. Because of this assumption, they misinterpret the binary format data:
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/phone_screenshot_standard.jpg"><br/>Standard SeedQR</td>
|
||||
<td align="center"><img src="img/phone_screenshot_compact.jpg"><br/>CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The clearly readable Standard SeedQR digit stream can be manually transcribed back to a mnemonic seed phrase with no other computer assistance, if need be.
|
||||
|
||||
It's just the above process in reverse:
|
||||
```bash
|
||||
# 12-word Standard SeedQR digit stream
|
||||
192402220235174306311124037817700641198012901210
|
||||
|
||||
# Separate out into 4-digit individual indices
|
||||
1924 0222 0235 1743 0631 1124 0378 1770 0641 1980 1290 1210
|
||||
|
||||
# Look up each BIP-39 index number (index+1 if using the github list!)
|
||||
1. vacuum 1924
|
||||
2. bridge 222
|
||||
3. buddy 235
|
||||
4. supreme 1743
|
||||
5. exclude 631
|
||||
6. milk 1124
|
||||
7. consider 378
|
||||
8. tail 1770
|
||||
9. expand 641
|
||||
10. wasp 1980
|
||||
11. pattern 1290
|
||||
12. nuclear 1210
|
||||
```
|
||||
|
||||
It would be much more difficult to manually recreate your seed from a CompactSeedQR. Tools like [zxing.org](https://zxing.org/w/decode.jspx) can help you get the binary data out as a hexidecimal string:
|
||||
|
||||
<img src="img/zxing_screenshot.png">
|
||||
|
||||
All 12-word CompactSeedQRs will start with `41 0` (that specifies the binary data format and says the length of the data is 16 bytes) and end with `0 ec` (unused byte and a half).
|
||||
|
||||
All 24-word CompactSeedQRs will start with `42 0` (binary, data length is 32 bytes) and end with `0` (unused half a byte).
|
||||
|
||||
The remaining hexidecimal data is just an alternate representation of the 128-bit entropy for your 12-word mnemonic (or 256 bits for a 24-word mnemonic). Various programming tools exist to convert from hex back into mnemonic form.
|
||||
|
||||
|
||||
## Obfuscation
|
||||
Conversely, having limited support for reading binary QR codes and the complications described above are seen by some as an added security feature. Should someone steal your CompactSeedQR or take a photo of it, they'll have to be fairly savvy to know how to decode it.
|
||||
|
||||
|
||||
# Some Additional Notes on QR Codes
|
||||
Our main use case is to be able to quickly initialize a SeedSigner with your Bitcoin private key. But using a QR code as your key loader--or even as your permanent backup etched in metal--has other advantages.
|
||||
|
||||
QR codes are ubiquitous now so plenty of hardware and software exists to read and generate them.
|
||||
|
||||
QR codes have built-in error correction. The "L" error correction mode is described as having a roughly 7% correction rate.
|
||||
|
||||
In general, QR codes are incredibly resilient to unreadable blocks. For example, you often see the middle blocks intentionally covered over with a logo:
|
||||
|
||||
<table align="center"><tr><td><img src="img/standard_24word_with_logo.png"></td></tr></table>
|
||||
|
||||
This is right at the limit of SeedSigner's ability to read this 24-word SeedQR, despite the inaccessible blocks.
|
||||
|
||||
Similarly, QR codes can withstand human error during the manual transcription process. A few mis-coded blocks, a smeared ink blob, or other physical blemishes will generally not cause any problems.
|
||||
|
||||
|
||||
# Test SeedQRs
|
||||
Sample test vectors to verify your own encode and decode implementations:
|
||||
|
||||
---
|
||||
|
||||
## Test Vector 1: 24-word seed
|
||||
```bash
|
||||
# 24-word seed:
|
||||
attack pizza motion avocado network gather crop fresh patrol unusual wild holiday candy pony ranch winter theme error hybrid van cereal salon goddess expire
|
||||
|
||||
# Standard SeedQR digit stream:
|
||||
011513251154012711900771041507421289190620080870026613431420201617920614089619290300152408010643
|
||||
|
||||
# CompactSeedQR bitstream:
|
||||
0000111001110100101101100100000100000111111110010100110011000000110011001111101011100110101000010011110111001011111011000011011001100010000101010100111111101100011001111110000011100000000010011001100111000000011110001001001001011001011111010001100100001010
|
||||
|
||||
# CompactSeedQR bytestream:
|
||||
b'\x0et\xb6A\x07\xf9L\xc0\xcc\xfa\xe6\xa1=\xcb\xec6b\x15O\xecg\xe0\xe0\t\x99\xc0x\x92Y}\x19\n'
|
||||
```
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector1_standard_24word.png"><br/>Standard SeedQR</td>
|
||||
<td align="center"><img src="img/vector1_compact_24word.png"><br/>CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## Test Vector 2: 24-word seed
|
||||
Note that this vector and a few others below include a null byte character (`\x00`) in its CompactSeedQR bytestream. This is a particularly troublesome character for most QR readers; most will read this character as an instruction to stop reading any further data.
|
||||
|
||||
Take extra care to confirm that your implementation correctly reads these characters and all remaining data after it!
|
||||
|
||||
```bash
|
||||
# 24-word seed:
|
||||
atom solve joy ugly ankle message setup typical bean era cactus various odor refuse element afraid meadow quick medal plate wisdom swap noble shallow
|
||||
|
||||
# Standard SeedQR digit stream:
|
||||
011416550964188800731119157218870156061002561932122514430573003611011405110613292018175411971576
|
||||
|
||||
# CompactSeedQR bitstream:
|
||||
0000111001011001110111011110001001110110000000001001001100010111111100010010011101011111000100111000100110001000100000000111100011001001100100110110100011010001111010000010010010001001101101011111011000101001010100110001111111000101101101101010010101101110
|
||||
|
||||
# CompactSeedQR bytestream:
|
||||
b"\x0eY\xdd\xe2v\x00\x93\x17\xf1'_\x13\x89\x88\x80x\xc9\x93h\xd1\xe8$\x89\xb5\xf6)S\x1f\xc5\xb6\xa5n"
|
||||
```
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector2_standard_24word.png"><br/>Standard SeedQR</td>
|
||||
<td align="center"><img src="img/vector2_compact_24word.png"><br/>CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## Test Vector 3: 24 word seed
|
||||
```bash
|
||||
# 24-word seed:
|
||||
sound federal bonus bleak light raise false engage round stock update render quote truck quality fringe palace foot recipe labor glow tortoise potato still
|
||||
|
||||
# Standard SeedQR digit stream:
|
||||
166206750203018810361417065805941507171219081456140818651401074412730727143709940798183613501710
|
||||
|
||||
# CompactSeedQR bitstream:
|
||||
1100111111001010100011000110010110001011110010000001100101100010010101001001001001010010101111000111101011000011101110100101101100001011000000011101001001101011110010101110100010011111001010110101111011001110101111100010011000111101110010110010101000110110
|
||||
|
||||
# CompactSeedQR bytestream:
|
||||
b'\xcf\xca\x8ce\x8b\xc8\x19bT\x92R\xbcz\xc3\xba[\x0b\x01\xd2k\xca\xe8\x9f+^\xce\xbe&=\xcb*6'
|
||||
```
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector3_standard_24word.png"><br/>Standard SeedQR</td>
|
||||
<td align="center"><img src="img/vector3_compact_24word.png"><br/>CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## Test Vector 4: 12-word seed
|
||||
```bash
|
||||
# 12-word seed:
|
||||
forum undo fragile fade shy sign arrest garment culture tube off merit
|
||||
|
||||
# Standard SeedQR digit stream:
|
||||
073318950739065415961602009907670428187212261116
|
||||
|
||||
# CompactSeedQR bitstream:
|
||||
01011011101111011001110101110001101010001110110001111001100100001000001100011010111111110011010110011101010000100110010101000101
|
||||
|
||||
# CompactSeedQR bytestream:
|
||||
b'[\xbd\x9dq\xa8\xecy\x90\x83\x1a\xff5\x9dBeE'
|
||||
```
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector4_standard_12word.png"><br/>Standard SeedQR</td>
|
||||
<td align="center"><img src="img/vector4_compact_12word.png"><br/>CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## Test Vector 5: 12-word seed
|
||||
```bash
|
||||
# 12-word seed:
|
||||
good battle boil exact add seed angle hurry success glad carbon whisper
|
||||
|
||||
# Standard SeedQR digit stream:
|
||||
080301540200062600251559007008931730078802752004
|
||||
|
||||
# CompactSeedQR bitstream:
|
||||
01100100011000100110100001100100001001110010000000110011100001011100001000110011011111011101100001001100010100001000100111111101
|
||||
|
||||
# CompactSeedQR bytestream:
|
||||
b"dbhd' 3\x85\xc23}\xd8LP\x89\xfd"
|
||||
```
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector5_standard_12word.png"><br/>Standard SeedQR</td>
|
||||
<td align="center"><img src="img/vector5_compact_12word.png"><br/>CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
## Test Vector 6: 12-word seed
|
||||
```bash
|
||||
# 12-word seed:
|
||||
approve fruit lens brass ring actual stool coin doll boss strong rate
|
||||
|
||||
# Standard SeedQR digit stream:
|
||||
008607501025021714880023171503630517020917211425
|
||||
|
||||
# CompactSeedQR bitstream:
|
||||
00001010110010111011101000000000100011011001101110100000000001011111010110011001011010110100000010100011010001110101110011011001
|
||||
|
||||
# CompactSeedQR bytestream:
|
||||
b'\n\xcb\xba\x00\x8d\x9b\xa0\x05\xf5\x99k@\xa3G\\\xd9'
|
||||
```
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center"><img src="img/vector6_standard_12word.png"><br/>Standard SeedQR</td>
|
||||
<td align="center"><img src="img/vector6_compact_12word.png"><br/>CompactSeedQR</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 325 KiB |
@@ -2,8 +2,8 @@ embit==0.4.10
|
||||
numpy==1.21.1
|
||||
picamera==1.13
|
||||
Pillow==8.2.0
|
||||
-e git+https://github.com/kdmukai/pyzbar.git@c3c237821c6a20b17953efe59b90df0b514a1c03#egg=pyzbar
|
||||
qrcode==7.3.1
|
||||
pyzbar==0.1.8
|
||||
qrcode==6.1
|
||||
RPi.GPIO==0.7.0
|
||||
six==1.16.0
|
||||
spidev==3.5
|
||||
|
||||
@@ -12,5 +12,4 @@ camera_rotation = 0
|
||||
network = main
|
||||
software = Prompt
|
||||
qr_density = 2
|
||||
custom_derivation = m/0/0
|
||||
compact_seedqr_enabled = False
|
||||
custom_derivation = m/0/0
|
||||
@@ -33,7 +33,7 @@ class Controller(Singleton):
|
||||
Note: In many/most cases you'll need to do the Controller import within a method
|
||||
rather than at the top in order avoid circular imports.
|
||||
"""
|
||||
VERSION = "0.4.6"
|
||||
VERSION = "0.4.5"
|
||||
|
||||
|
||||
@classmethod
|
||||
@@ -46,17 +46,7 @@ class Controller(Singleton):
|
||||
|
||||
|
||||
@classmethod
|
||||
def configure_instance(cls, config=None, disable_hardware=False):
|
||||
"""
|
||||
- `disable_hardware` is only meant to be used by the test suite so that it
|
||||
can keep re-initializing a Controller in however many tests it needs to. But
|
||||
this is only possible if the hardware isn't already being reserved. Without
|
||||
this you get:
|
||||
|
||||
RuntimeError: Conflicting edge detection already enabled for this GPIO channel
|
||||
|
||||
each time you try to re-initialize a Controller.
|
||||
"""
|
||||
def configure_instance(cls, config=None):
|
||||
# Must be called before the first get_instance() call
|
||||
if cls._instance:
|
||||
raise Exception("Instance already configured")
|
||||
@@ -66,10 +56,7 @@ class Controller(Singleton):
|
||||
cls._instance = controller
|
||||
|
||||
# Input Buttons
|
||||
if disable_hardware:
|
||||
controller.buttons = None
|
||||
else:
|
||||
controller.buttons = Buttons()
|
||||
controller.buttons = Buttons()
|
||||
|
||||
# models
|
||||
controller.storage = SeedStorage()
|
||||
@@ -172,8 +159,6 @@ class Controller(Singleton):
|
||||
ret_val = self.show_persistent_settings_tool()
|
||||
elif ret_val == Path.CAMERA_ROTATION:
|
||||
ret_val = self.show_camera_rotation_tool()
|
||||
elif ret_val == Path.COMPACT_SEEDQR_ENABLED:
|
||||
ret_val = self.show_compact_seedqr_enabled()
|
||||
elif ret_val == Path.DONATE:
|
||||
ret_val = self.show_donate_tool()
|
||||
elif ret_val == Path.RESET:
|
||||
@@ -1126,14 +1111,6 @@ class Controller(Singleton):
|
||||
|
||||
return Path.SETTINGS_SUB_MENU ### Show Donate Screen and QR
|
||||
|
||||
|
||||
def show_compact_seedqr_enabled(self):
|
||||
r = self.settings_tools_view.display_compact_seedqr_enabled()
|
||||
if r is not None:
|
||||
self.settings.compact_seedqr_enabled = r
|
||||
|
||||
return Path.SETTINGS_SUB_MENU ### Show Donate Screen and QR
|
||||
|
||||
def show_donate_tool(self):
|
||||
self.settings_tools_view.display_donate_info_screen()
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ class Path:
|
||||
QR_DENSITY_SETTING = 65
|
||||
PERSISTENT_SETTINGS = 67
|
||||
CAMERA_ROTATION = 69
|
||||
COMPACT_SEEDQR_ENABLED = 70
|
||||
RESET = 68
|
||||
|
||||
# Seed Slots
|
||||
|
||||
@@ -1,40 +1,17 @@
|
||||
import qrcode
|
||||
from qrcode.image.styledpil import StyledPilImage
|
||||
from qrcode.image.styles.moduledrawers import CircleModuleDrawer, GappedSquareModuleDrawer
|
||||
from PIL import Image
|
||||
import subprocess
|
||||
|
||||
class QR:
|
||||
STYLE__DEFAULT = 1
|
||||
STYLE__ROUNDED = 2
|
||||
STYLE__GRID = 3
|
||||
|
||||
def __init__(self) -> None:
|
||||
return
|
||||
|
||||
def qrimage(self, data, width=240, height=240, border=3, style=None):
|
||||
def qrimage(self, data, width=240, height=240, border=3):
|
||||
qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=5, border=border )
|
||||
qr.add_data(data)
|
||||
qr.make(fit=True)
|
||||
if not style or style == QR.STYLE__DEFAULT:
|
||||
return qr.make_image(fill_color="black", back_color="white").resize((width,height)).convert('RGBA')
|
||||
else:
|
||||
if style == QR.STYLE__ROUNDED:
|
||||
return qr.make_image(
|
||||
fill_color="black",
|
||||
back_color="white",
|
||||
image_factory=StyledPilImage,
|
||||
module_drawer=CircleModuleDrawer()
|
||||
).resize((width,height)).convert('RGBA')
|
||||
|
||||
elif style == QR.STYLE__GRID:
|
||||
return qr.make_image(
|
||||
fill_color="black",
|
||||
back_color="white",
|
||||
image_factory=StyledPilImage,
|
||||
module_drawer=GappedSquareModuleDrawer()
|
||||
).resize((width,height)).convert('RGBA')
|
||||
|
||||
return(qr.make_image(fill_color="black", back_color="white").resize((width,height)).convert('RGBA'))
|
||||
|
||||
def qrimage_io(self, data, width=240, height=240, border=3, background="FFFFFF"):
|
||||
if 1 <= border <= 10:
|
||||
@@ -50,4 +27,4 @@ class QR:
|
||||
return self.qrimage(data,width,height,border)
|
||||
img = Image.open("/dev/shm/qrcode.png").resize((width,height), Image.NEAREST).convert("RGBA")
|
||||
|
||||
return img
|
||||
return img
|
||||
@@ -3,18 +3,13 @@ from pyzbar.pyzbar import ZBarSymbol
|
||||
from enum import IntEnum
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import base64
|
||||
from embit import psbt, bip39
|
||||
from embit import psbt
|
||||
from binascii import a2b_base64, b2a_base64
|
||||
from seedsigner.helpers.ur2.ur_decoder import URDecoder
|
||||
from seedsigner.helpers.bcur import (cbor_decode, bc32decode)
|
||||
from seedsigner.models import QRType, Seed
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
###
|
||||
### DecodeQR Class
|
||||
### Purpose: used to process images or string data from animated qr codes with psbt data to create binary/base64 psbt
|
||||
@@ -46,40 +41,23 @@ class DecodeQR:
|
||||
self.seedqr = SeedQR(wordlist=self.wordlist)
|
||||
|
||||
def addImage(self, image):
|
||||
data = DecodeQR.extract_qr_data(image, is_binary=True)
|
||||
if data == None:
|
||||
qr_str = DecodeQR.QR2Str(image)
|
||||
if qr_str == None:
|
||||
return DecodeQRStatus.FALSE
|
||||
|
||||
return self.add_data(data)
|
||||
return self.addString(qr_str)
|
||||
|
||||
def add_data(self, data):
|
||||
if data == None:
|
||||
def addString(self, qr_str):
|
||||
if qr_str == None:
|
||||
return DecodeQRStatus.FALSE
|
||||
|
||||
qr_type = DecodeQR.SegmentType(data, wordlist=self.wordlist)
|
||||
qr_type = DecodeQR.SegmentType(qr_str, wordlist=self.wordlist)
|
||||
|
||||
if self.qr_type == None:
|
||||
self.qr_type = qr_type
|
||||
elif self.qr_type != qr_type:
|
||||
raise Exception('QR Fragement Unexpected Type Change')
|
||||
|
||||
# Process the binary formats first
|
||||
if self.qr_type == QRType.COMPACTSEEDQR:
|
||||
rt = self.seedqr.add(data, QRType.COMPACTSEEDQR)
|
||||
if rt == DecodeQRStatus.COMPLETE:
|
||||
self.complete = True
|
||||
return rt
|
||||
|
||||
# Convert to string data
|
||||
if type(data) == bytes:
|
||||
# Should always be bytes, but the test suite has some manual datasets that
|
||||
# are strings.
|
||||
# TODO: Convert the test suite rather than handle here?
|
||||
qr_str = data.decode('utf-8')
|
||||
else:
|
||||
# it's already str data
|
||||
qr_str = data
|
||||
|
||||
if self.qr_type == QRType.PSBTUR2:
|
||||
|
||||
self.ur_decoder.receive_part(qr_str)
|
||||
@@ -116,8 +94,8 @@ class DecodeQR:
|
||||
self.complete = True
|
||||
return rt
|
||||
|
||||
elif self.qr_type == QRType.SEEDQR:
|
||||
rt = self.seedqr.add(qr_str, QRType.SEEDQR)
|
||||
elif self.qr_type == QRType.SEEDSSQR:
|
||||
rt = self.seedqr.add(qr_str, QRType.SEEDSSQR)
|
||||
if rt == DecodeQRStatus.COMPLETE:
|
||||
self.complete = True
|
||||
return rt
|
||||
@@ -235,7 +213,7 @@ class DecodeQR:
|
||||
return False
|
||||
|
||||
def isSeed(self):
|
||||
if self.qr_type in (QRType.SEEDQR, QRType.COMPACTSEEDQR, QRType.SEEDUR2, QRType.SEEDMNEMONIC, QRType.SEED4LETTERMNEMONIC):
|
||||
if self.qr_type in (QRType.SEEDSSQR, QRType.SEEDUR2, QRType.SEEDMNEMONIC, QRType.SEED4LETTERMNEMONIC):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -253,95 +231,62 @@ class DecodeQR:
|
||||
return self.qr_type
|
||||
|
||||
@staticmethod
|
||||
def extract_qr_data(image, is_binary=False) -> str:
|
||||
def QR2Str(image) -> str:
|
||||
if image is None:
|
||||
return None
|
||||
|
||||
# Are there any cases where defaulting `binary` to True causes problems?
|
||||
barcodes = pyzbar.decode(image, symbols=[ZBarSymbol.QRCODE], binary=is_binary)
|
||||
|
||||
if barcodes:
|
||||
logger.debug("--------------- extract_qr_data ---------------")
|
||||
logger.debug(barcodes)
|
||||
barcodes = pyzbar.decode(image, symbols=[ZBarSymbol.QRCODE])
|
||||
|
||||
for barcode in barcodes:
|
||||
# Only pull and return the first barcode
|
||||
return barcode.data
|
||||
return barcode.data.decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def SegmentType(s, wordlist=None):
|
||||
logger.debug("-------------- DecodeQR.SegmentType --------------")
|
||||
logger.debug(type(s))
|
||||
logger.debug(len(s))
|
||||
|
||||
# PSBT
|
||||
if re.search("^UR:CRYPTO-PSBT/", s, re.IGNORECASE):
|
||||
return QRType.PSBTUR2
|
||||
elif re.search(r'^p(\d+)of(\d+) ([A-Za-z0-9+\/=]+$)', s, re.IGNORECASE): #must be base64 characters only in segment
|
||||
return QRType.PSBTSPECTER
|
||||
elif re.search("^UR:BYTES/", s, re.IGNORECASE):
|
||||
return QRType.PSBTURLEGACY
|
||||
elif DecodeQR.isBase64PSBT(s):
|
||||
return QRType.PSBTBASE64
|
||||
|
||||
# Wallet Descriptor
|
||||
desc_str = s.replace("\n","").replace(" ","")
|
||||
if re.search(r'^p(\d+)of(\d+) ', s, re.IGNORECASE):
|
||||
# when not a SPECTER Base64 PSBT from above, assume it's json
|
||||
return QRType.SPECTERWALLETQR
|
||||
elif re.search(r'^\{\"label\".*\"descriptor\"\:.*', desc_str, re.IGNORECASE):
|
||||
# if json starting with label and contains descriptor, assume specter wallet json
|
||||
return QRType.SPECTERWALLETQR
|
||||
|
||||
# create 4 letter wordlist only if not PSBT (performance gain)
|
||||
try:
|
||||
# Convert to str data
|
||||
if type(s) == bytes:
|
||||
# Should always be bytes, but the test suite has some manual datasets that
|
||||
# are strings.
|
||||
# TODO: Convert the test suite rather than handle here?
|
||||
s = s.decode('utf-8')
|
||||
# PSBT
|
||||
if re.search("^UR:CRYPTO-PSBT/", s, re.IGNORECASE):
|
||||
return QRType.PSBTUR2
|
||||
elif re.search(r'^p(\d+)of(\d+) ([A-Za-z0-9+\/=]+$)', s, re.IGNORECASE): #must be base64 characters only in segment
|
||||
return QRType.PSBTSPECTER
|
||||
elif re.search("^UR:BYTES/", s, re.IGNORECASE):
|
||||
return QRType.PSBTURLEGACY
|
||||
elif DecodeQR.isBase64PSBT(s):
|
||||
return QRType.PSBTBASE64
|
||||
|
||||
# Wallet Descriptor
|
||||
desc_str = s.replace("\n","").replace(" ","")
|
||||
if re.search(r'^p(\d+)of(\d+) ', s, re.IGNORECASE):
|
||||
# when not a SPECTER Base64 PSBT from above, assume it's json
|
||||
return QRType.SPECTERWALLETQR
|
||||
elif re.search(r'^\{\"label\".*\"descriptor\"\:.*', desc_str, re.IGNORECASE):
|
||||
# if json starting with label and contains descriptor, assume specter wallet json
|
||||
return QRType.SPECTERWALLETQR
|
||||
|
||||
# create 4 letter wordlist only if not PSBT (performance gain)
|
||||
try:
|
||||
_4LETTER_WORDLIST = [word[:4].strip() for word in wordlist]
|
||||
except:
|
||||
_4LETTER_WORDLIST = []
|
||||
|
||||
# Seed
|
||||
if re.search(r'\d{48,96}', s):
|
||||
return QRType.SEEDQR
|
||||
|
||||
# Bitcoin Address
|
||||
elif DecodeQR.isBitcoinAddress(s):
|
||||
return QRType.BITCOINADDRESSQR
|
||||
|
||||
# Seed
|
||||
elif all(x in wordlist for x in s.strip().split(" ")):
|
||||
# checks if all words in list are in bip39 word list
|
||||
return QRType.SEEDMNEMONIC
|
||||
elif all(x in _4LETTER_WORDLIST for x in s.strip().split(" ")):
|
||||
# checks if all 4 letter words are in list are in 4 letter bip39 word list
|
||||
return QRType.SEED4LETTERMNEMONIC
|
||||
elif DecodeQR.isBase43PSBT(s):
|
||||
return QRType.PSBTBASE43
|
||||
except UnicodeDecodeError:
|
||||
# Probably this isn't meant to be string data; check if it's valid byte data
|
||||
# below.
|
||||
pass
|
||||
_4LETTER_WORDLIST = [word[:4].strip() for word in wordlist]
|
||||
except:
|
||||
_4LETTER_WORDLIST = []
|
||||
|
||||
# Seed
|
||||
if re.search(r'\d{48,96}', s):
|
||||
return QRType.SEEDSSQR
|
||||
|
||||
# Bitcoin Address
|
||||
elif DecodeQR.isBitcoinAddress(s):
|
||||
return QRType.BITCOINADDRESSQR
|
||||
|
||||
# Seed
|
||||
elif all(x in wordlist for x in s.strip().split(" ")):
|
||||
# checks if all words in list are in bip39 word list
|
||||
return QRType.SEEDMNEMONIC
|
||||
elif all(x in _4LETTER_WORDLIST for x in s.strip().split(" ")):
|
||||
# checks if all 4 letter words are in list are in 4 letter bip39 word list
|
||||
return QRType.SEED4LETTERMNEMONIC
|
||||
elif DecodeQR.isBase43PSBT(s):
|
||||
return QRType.PSBTBASE43
|
||||
|
||||
# Is it byte data?
|
||||
# 32 bytes for 24-word CompactSeedQR; 16 bytes for 12-word CompactSeedQR
|
||||
if len(s) == 32 or len(s) == 16:
|
||||
try:
|
||||
bitstream = ""
|
||||
for b in s:
|
||||
bitstream += bin(b).lstrip('0b').zfill(8)
|
||||
logger.debug(bitstream)
|
||||
|
||||
return QRType.COMPACTSEEDQR
|
||||
except Exception as e:
|
||||
# Couldn't extract byte data; assume it's not a byte format
|
||||
pass
|
||||
|
||||
return QRType.INVALID
|
||||
|
||||
@staticmethod
|
||||
@@ -587,7 +532,6 @@ class Base64DecodeQR:
|
||||
|
||||
return None
|
||||
|
||||
# TODO: Should this be static? Need to fix 'self' reference here
|
||||
@staticmethod
|
||||
def currentSegmentNum(segment) -> int:
|
||||
return self.collected_segments
|
||||
@@ -626,7 +570,6 @@ class Base43DecodeQR:
|
||||
def getData(self):
|
||||
return self.data
|
||||
|
||||
# TODO: Should this be static? Need to fix 'self' reference here
|
||||
@staticmethod
|
||||
def currentSegmentNum(segment) -> int:
|
||||
return self.collected_segments
|
||||
@@ -659,11 +602,11 @@ class SeedQR:
|
||||
if self.wordlist == None:
|
||||
raise Exception('Wordlist Required')
|
||||
|
||||
def add(self, segment, qr_type=QRType.SEEDQR):
|
||||
# `segment` data will either be bytes or str, depending on the qr_type
|
||||
logger.debug(qr_type)
|
||||
def add(self, segment, qr_type=QRType.SEEDSSQR):
|
||||
|
||||
if qr_type == QRType.SEEDQR:
|
||||
_4LETTER_WORDLIST = [word[:4].strip() for word in self.wordlist]
|
||||
|
||||
if qr_type == QRType.SEEDSSQR:
|
||||
|
||||
try:
|
||||
self.seed_phrase = []
|
||||
@@ -685,17 +628,8 @@ class SeedQR:
|
||||
except Exception as e:
|
||||
return DecodeQRStatus.INVALID
|
||||
|
||||
if qr_type == QRType.COMPACTSEEDQR:
|
||||
try:
|
||||
self.seed_phrase = bip39.mnemonic_from_bytes(segment).split()
|
||||
self.complete = True
|
||||
self.collected_segments = 1
|
||||
return DecodeQRStatus.COMPLETE
|
||||
except Exception as e:
|
||||
logger.exception(repr(e))
|
||||
return DecodeQRStatus.INVALID
|
||||
|
||||
elif qr_type == QRType.SEEDMNEMONIC:
|
||||
|
||||
try:
|
||||
# embit mnemonic code to validate
|
||||
seed = Seed(segment, passphrase="", wordlist=self.wordlist)
|
||||
@@ -717,8 +651,6 @@ class SeedQR:
|
||||
sl = segment.strip().split(" ")
|
||||
words = []
|
||||
for s in sl:
|
||||
# TODO: Pre-calculate this once on startup
|
||||
_4LETTER_WORDLIST = [word[:4].strip() for word in self.wordlist]
|
||||
words.append(self.wordlist[_4LETTER_WORDLIST.index(s)])
|
||||
|
||||
# embit mnemonic code to validate
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import math
|
||||
|
||||
from enum import IntEnum
|
||||
from embit import psbt, bip32
|
||||
from embit.networks import NETWORKS
|
||||
@@ -63,10 +61,8 @@ class EncodeQR:
|
||||
self.encoder = SpecterEncodePSBTQR(self.psbt, self.qr_density)
|
||||
elif self.qr_type == QRType.PSBTUR2:
|
||||
self.encoder = UREncodePSBTQR(self.psbt, self.qr_density)
|
||||
elif self.qr_type == QRType.SEEDQR:
|
||||
self.encoder = SeedQR(self.seed_phrase, self.wordlist)
|
||||
elif self.qr_type == QRType.COMPACTSEEDQR:
|
||||
self.encoder = CompactSeedQR(self.seed_phrase, self.wordlist)
|
||||
elif self.qr_type == QRType.SEEDSSQR:
|
||||
self.encoder = SeedSSQR(self.seed_phrase, self.wordlist)
|
||||
elif self.qr_type == QRType.XPUBQR:
|
||||
self.encoder = XPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.wordlist)
|
||||
elif self.qr_type == QRType.SPECTERXPUBQR:
|
||||
@@ -85,7 +81,7 @@ class EncodeQR:
|
||||
|
||||
def nextPartImage(self, width=240, height=240, border=3, background="FFFFFF"):
|
||||
part = self.nextPart()
|
||||
if self.qr_type == QRType.SEEDQR:
|
||||
if self.qr_type == QRType.SEEDSSQR:
|
||||
return self.qr.qrimage(part, width, height, border)
|
||||
else:
|
||||
return self.qr.qrimage_io(part, width, height, border, background=background)
|
||||
@@ -194,9 +190,8 @@ class SpecterEncodePSBTQR:
|
||||
def isComplete(self):
|
||||
return self.sent_complete
|
||||
|
||||
class SeedSSQR:
|
||||
|
||||
|
||||
class SeedQR:
|
||||
def __init__(self, seed_phrase, wordlist):
|
||||
self.seed_phrase = seed_phrase
|
||||
self.wordlist = wordlist
|
||||
@@ -204,57 +199,22 @@ class SeedQR:
|
||||
if self.wordlist == None:
|
||||
raise Exception('Wordlist Required')
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
return 1
|
||||
|
||||
|
||||
def nextPart(self):
|
||||
data = ""
|
||||
|
||||
# Output as Numeric data format
|
||||
for word in self.seed_phrase:
|
||||
index = self.wordlist.index(word)
|
||||
data += str("%04d" % index)
|
||||
|
||||
return data
|
||||
|
||||
return data
|
||||
|
||||
def isComplete(self):
|
||||
return True
|
||||
|
||||
|
||||
class CompactSeedQR(SeedQR):
|
||||
def nextPart(self):
|
||||
# Output as binary data format
|
||||
binary_str = ""
|
||||
for word in self.seed_phrase:
|
||||
index = self.wordlist.index(word)
|
||||
|
||||
# Convert index to binary, strip out '0b' prefix; zero-pad to 11 bits
|
||||
binary_str += bin(index).split('b')[1].zfill(11)
|
||||
|
||||
# We can exclude the checksum bits at the end
|
||||
if len(self.seed_phrase) == 24:
|
||||
# 8 checksum bits in a 24-word seed
|
||||
binary_str = binary_str[:-8]
|
||||
|
||||
elif len(self.seed_phrase) == 12:
|
||||
# 4 checksum bits in a 12-word seed
|
||||
binary_str = binary_str[:-4]
|
||||
|
||||
# Now convert to bytes, 8 bits at a time
|
||||
as_bytes = bytearray()
|
||||
for i in range(0, math.ceil(len(binary_str) / 8)):
|
||||
# int conversion reads byte data as a string prefixed with '0b'
|
||||
as_bytes.append(int('0b' + binary_str[i*8:(i+1)*8], 2))
|
||||
|
||||
# Must return data as `bytes` for `qrcode` to properly recognize it as byte data
|
||||
return bytes(as_bytes)
|
||||
|
||||
|
||||
|
||||
class XPubQR:
|
||||
|
||||
def __init__(self, seed_phrase, passphrase, derivation, network, wordlist):
|
||||
self.seed_phrase = seed_phrase
|
||||
self.passphrase = passphrase
|
||||
|
||||
@@ -11,8 +11,7 @@ class QRType(IntEnum):
|
||||
PSBTURLEGACY = 3
|
||||
PSBTBASE43 = 4
|
||||
PSBTUR2 = 5
|
||||
SEEDQR = 6
|
||||
COMPACTSEEDQR = 10
|
||||
SEEDSSQR = 6
|
||||
SEEDUR2 = 7
|
||||
SEEDMNEMONIC = 8
|
||||
SEED4LETTERMNEMONIC = 9
|
||||
|
||||
@@ -34,8 +34,7 @@ class Settings(Singleton):
|
||||
'network': "main",
|
||||
'software': "Specter Desktop",
|
||||
'qr_density': EncodeQRDensity.MEDIUM,
|
||||
'custom_derivation': 'm/0/0',
|
||||
'compact_seedqr_enabled': False,
|
||||
'custom_derivation': 'm/0/0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,21 +47,16 @@ class Settings(Singleton):
|
||||
settings.init_complete = True
|
||||
|
||||
def __config_to_data(self, config):
|
||||
# TODO: Make each get resilient to the possibility of the field being missing in
|
||||
# the `settings.ini`
|
||||
self.persistent = config.getboolean("system", "persistent_settings")
|
||||
self._data["system"]["debug"] = config.getboolean("system", "debug")
|
||||
self._data["system"]["default_language"] = config["system"]["default_language"]
|
||||
self._data["display"]["text_color"] = config["display"]["text_color"]
|
||||
self.qr_background_color = config["display"]["qr_background_color"]
|
||||
self._data["display"]["camera_rotation"] = int(config["display"]["camera_rotation"])
|
||||
self.network = config["wallet"]["network"]
|
||||
self.software = config["wallet"]["software"]
|
||||
self.qr_density = int(config["wallet"]["qr_density"])
|
||||
self.custom_derivation = config["wallet"]["custom_derivation"]
|
||||
|
||||
if "compact_seedqr_enabled"in config["wallet"]:
|
||||
self.compact_seedqr_enabled = config.getboolean("wallet", "compact_seedqr_enabled")
|
||||
self.qr_background_color = config["display"]["qr_background_color"]
|
||||
|
||||
### persistent settings handling
|
||||
|
||||
@@ -231,17 +225,7 @@ class Settings(Singleton):
|
||||
def custom_derivation(self, value):
|
||||
# TODO: parse and validate custom derivation path
|
||||
self._data["wallet"]["custom_derivation"] = value
|
||||
self.__writeConfig()
|
||||
|
||||
@property
|
||||
def compact_seedqr_enabled(self):
|
||||
return self._data["wallet"]["compact_seedqr_enabled"]
|
||||
|
||||
@compact_seedqr_enabled.setter
|
||||
def compact_seedqr_enabled(self, value):
|
||||
self._data["wallet"]["compact_seedqr_enabled"] = value
|
||||
self.__writeConfig()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def calc_derivation(network, wallet_type, script_type):
|
||||
if network == "main":
|
||||
|
||||
@@ -124,20 +124,13 @@ class MenuView(View):
|
||||
### Settings Menu
|
||||
|
||||
def display_settings_menu(self) -> int:
|
||||
lines = [
|
||||
"... [ Return to Main ]",
|
||||
f"Wallet: {Settings.get_instance().software}",
|
||||
f"Network: {Settings.get_instance().network}",
|
||||
f"QR Density: {Settings.get_instance().qr_density_name}",
|
||||
"Input / Output Tests",
|
||||
f"Persistent Settings: {Settings.get_instance().persistent_display}",
|
||||
f"Camera Rotation: {Settings.get_instance().camera_rotation}°",
|
||||
f"Compact SeedQR: {'Enabled' if Settings.get_instance().compact_seedqr_enabled else 'Disabled'}",
|
||||
"Version Info",
|
||||
"Donate to SeedSigner",
|
||||
"Reset SeedSigner"
|
||||
]
|
||||
lines = ["... [ Return to Main ]", "Wallet: <wallet>", "Network: <network>", "QR Density: <density>", "Input / Output Tests", "Persistent Settings: <persistent>", "Camera Rotation", "Version Info", "Donate to SeedSigner", "Reset SeedSigner"]
|
||||
input = 0
|
||||
|
||||
lines[1] = lines[1].replace("<wallet>", Settings.get_instance().software)
|
||||
lines[2] = lines[2].replace("<network>", Settings.get_instance().network)
|
||||
lines[3] = lines[3].replace("<density>", Settings.get_instance().qr_density_name)
|
||||
lines[5] = lines[5].replace("<persistent>", Settings.get_instance().persistent_display)
|
||||
|
||||
# Draw Menu
|
||||
self.selected_menu_num = 1
|
||||
@@ -166,12 +159,10 @@ class MenuView(View):
|
||||
elif self.selected_menu_num == 7:
|
||||
return Path.CAMERA_ROTATION
|
||||
elif self.selected_menu_num == 8:
|
||||
return Path.COMPACT_SEEDQR_ENABLED
|
||||
elif self.selected_menu_num == 9:
|
||||
return Path.VERSION_INFO
|
||||
elif self.selected_menu_num == 10:
|
||||
elif self.selected_menu_num == 9:
|
||||
return Path.DONATE
|
||||
elif self.selected_menu_num == 11:
|
||||
elif self.selected_menu_num == 10:
|
||||
return Path.RESET
|
||||
raise Exception("Unhandled case")
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# External Dependencies
|
||||
from re import L
|
||||
from PIL import ImageDraw, Image
|
||||
from PIL.ImageOps import autocontrast
|
||||
|
||||
import hashlib
|
||||
import math
|
||||
import os
|
||||
@@ -967,10 +965,7 @@ class SeedToolsView(View):
|
||||
|
||||
elif show_qr_option and ret_val == B.KEY_RIGHT:
|
||||
# Show the resulting seed as a transcribable QR code
|
||||
if self.controller.settings.compact_seedqr_enabled:
|
||||
self.display_seed_qr_options(seed_phrase)
|
||||
else:
|
||||
self.seed_phrase_as_qr(seed_phrase)
|
||||
self.seed_phrase_as_qr(seed_phrase)
|
||||
|
||||
# Signal success to move forward
|
||||
return True
|
||||
@@ -997,10 +992,7 @@ class SeedToolsView(View):
|
||||
|
||||
elif show_qr_option and ret_val == B.KEY_RIGHT:
|
||||
# Show the resulting seed as a transcribable QR code
|
||||
if self.controller.settings.compact_seedqr_enabled:
|
||||
self.display_seed_qr_options(seed_phrase)
|
||||
else:
|
||||
self.seed_phrase_as_qr(seed_phrase)
|
||||
self.seed_phrase_as_qr(seed_phrase)
|
||||
|
||||
# Signal success to move forward
|
||||
return True
|
||||
@@ -1012,38 +1004,9 @@ class SeedToolsView(View):
|
||||
return True
|
||||
|
||||
|
||||
def display_seed_qr_options(self, seed_phrase) -> int:
|
||||
if len(seed_phrase) == 24:
|
||||
standard = "29x29"
|
||||
compact = "25x25"
|
||||
else:
|
||||
standard = "25x25"
|
||||
compact = "21x21"
|
||||
r = self.controller.menu_view.display_generic_selection_menu(
|
||||
["... [ Return to Seed Tools ]", f"Standard SeedQR: {standard}", f"Compact SeedQR: {compact}"],
|
||||
"Transcribe SeedQR"
|
||||
)
|
||||
if r == 2:
|
||||
return self.seed_phrase_as_qr(seed_phrase, is_compact_seedqr=False)
|
||||
elif r == 3:
|
||||
return self.seed_phrase_as_qr(seed_phrase, is_compact_seedqr=True)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def seed_phrase_as_qr(self, seed_phrase, is_compact_seedqr=False):
|
||||
if is_compact_seedqr:
|
||||
e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.COMPACTSEEDQR, wordlist=self.controller.settings.wordlist)
|
||||
else:
|
||||
e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.SEEDQR, wordlist=self.controller.settings.wordlist)
|
||||
data = e.nextPart()
|
||||
qr = QR()
|
||||
image = qr.qrimage(
|
||||
data=data,
|
||||
width=240,
|
||||
height=240,
|
||||
border=3,
|
||||
style=QR.STYLE__ROUNDED).convert("RGBA")
|
||||
def seed_phrase_as_qr(self, seed_phrase):
|
||||
e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.SEEDSSQR, wordlist=self.controller.settings.wordlist)
|
||||
image = e.nextPartImage(240, 240, 3)
|
||||
View.DispShowImageWithText(image, "click to zoom, right to exit", font=View.ASSISTANT18, text_color="BLACK", text_background="ORANGE")
|
||||
|
||||
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_PRESS])
|
||||
@@ -1053,35 +1016,15 @@ class SeedToolsView(View):
|
||||
elif input == B.KEY_PRESS:
|
||||
# Render an oversized QR code that we can view up close
|
||||
pixels_per_block = 24
|
||||
|
||||
# Border must accommodate the 3 blocks outside the center 5x5 mask plus up to
|
||||
# 4 empty blocks inside the 5x5 mask (21x21 has a 1-block final col/row).
|
||||
qr_border = 7
|
||||
qr_blocks_per_zoom = 5
|
||||
if len(seed_phrase) == 24:
|
||||
if is_compact_seedqr:
|
||||
num_modules = 25
|
||||
else:
|
||||
num_modules = 29
|
||||
else:
|
||||
if is_compact_seedqr:
|
||||
num_modules = 21
|
||||
|
||||
# Optimize for 21x21
|
||||
qr_blocks_per_zoom = 7
|
||||
else:
|
||||
num_modules = 25
|
||||
|
||||
width = (qr_border + num_modules + qr_border) * pixels_per_block
|
||||
qr_border = 4
|
||||
width = (qr_border + 25 + qr_border) * pixels_per_block
|
||||
height = width
|
||||
if len(seed_phrase) == 24:
|
||||
width = (qr_border + 29 + qr_border) * pixels_per_block
|
||||
height = width
|
||||
data = e.nextPart()
|
||||
qr = QR()
|
||||
image = qr.qrimage(
|
||||
data,
|
||||
width=width,
|
||||
height=height,
|
||||
border=qr_border,
|
||||
style=QR.STYLE__ROUNDED).convert("RGBA")
|
||||
image = qr.qrimage(data, width=width, height=height, border=qr_border).convert("RGBA")
|
||||
|
||||
# Render gridlines but leave the 1-block border as-is
|
||||
draw = ImageDraw.Draw(image)
|
||||
@@ -1094,8 +1037,8 @@ class SeedToolsView(View):
|
||||
block_mask = Image.new("RGBA", (View.canvas_width, View.canvas_height), (255,255,255,0))
|
||||
draw = ImageDraw.Draw(block_mask)
|
||||
|
||||
mask_width = int((View.canvas_width - qr_blocks_per_zoom * pixels_per_block)/2)
|
||||
mask_height = int((View.canvas_height - qr_blocks_per_zoom * pixels_per_block)/2)
|
||||
mask_width = int((View.canvas_width - 5 * pixels_per_block)/2)
|
||||
mask_height = int((View.canvas_height - 5 * pixels_per_block)/2)
|
||||
mask_rgba = (0, 0, 0, 226)
|
||||
draw.rectangle((0, 0, View.canvas_width, mask_height), fill=mask_rgba)
|
||||
draw.rectangle((0, View.canvas_height - mask_height - 1, View.canvas_width, View.canvas_height), fill=mask_rgba)
|
||||
@@ -1151,27 +1094,29 @@ class SeedToolsView(View):
|
||||
)
|
||||
|
||||
while True:
|
||||
# View.draw_text_over_image("click to exit", font=View.ASSISTANT18, text_color="BLACK", text_background="ORANGE")
|
||||
|
||||
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT, B.KEY_UP, B.KEY_DOWN, B.KEY_PRESS])
|
||||
if input == B.KEY_RIGHT:
|
||||
next_x = cur_x + qr_blocks_per_zoom * pixels_per_block
|
||||
next_x = cur_x + 5 * pixels_per_block
|
||||
cur_block_x += 1
|
||||
if next_x > width - View.canvas_width:
|
||||
next_x = cur_x
|
||||
cur_block_x -= 1
|
||||
elif input == B.KEY_LEFT:
|
||||
next_x = cur_x - qr_blocks_per_zoom * pixels_per_block
|
||||
next_x = cur_x - 5 * pixels_per_block
|
||||
cur_block_x -= 1
|
||||
if next_x < 0:
|
||||
next_x = cur_x
|
||||
cur_block_x += 1
|
||||
elif input == B.KEY_DOWN:
|
||||
next_y = cur_y + qr_blocks_per_zoom * pixels_per_block
|
||||
next_y = cur_y + 5 * pixels_per_block
|
||||
cur_block_y += 1
|
||||
if next_y > height - View.canvas_height:
|
||||
next_y = cur_y
|
||||
cur_block_y -= 1
|
||||
elif input == B.KEY_UP:
|
||||
next_y = cur_y - qr_blocks_per_zoom * pixels_per_block
|
||||
next_y = cur_y - 5 * pixels_per_block
|
||||
cur_block_y -= 1
|
||||
if next_y < 0:
|
||||
next_y = cur_y
|
||||
|
||||
@@ -64,6 +64,7 @@ class SettingsToolsView(View):
|
||||
return None
|
||||
|
||||
def display_persistent_settings(self) -> bool:
|
||||
|
||||
lines = ["... [ Return to Settings ]"]
|
||||
lines.append("Yes")
|
||||
lines.append("No")
|
||||
@@ -81,10 +82,10 @@ class SettingsToolsView(View):
|
||||
|
||||
def display_camera_rotation(self) -> int:
|
||||
lines = ["... [ Return to Settings ]"]
|
||||
lines.append("0° (default)")
|
||||
lines.append("90°")
|
||||
lines.append("180°")
|
||||
lines.append("270°")
|
||||
lines.append("0 (default)")
|
||||
lines.append("90")
|
||||
lines.append("180")
|
||||
lines.append("270")
|
||||
|
||||
r = self.controller.menu_view.display_generic_selection_menu(lines, "Camera Rotation")
|
||||
if r == 1:
|
||||
@@ -122,6 +123,7 @@ class SettingsToolsView(View):
|
||||
###
|
||||
### Custom Derivation Path
|
||||
###
|
||||
|
||||
def draw_derivation_keyboard_entry(self, existing_derivation = "m/"):
|
||||
def render_right_panel():
|
||||
row_height = 28
|
||||
@@ -271,20 +273,4 @@ class SettingsToolsView(View):
|
||||
# Render the text entry display and cursor block
|
||||
text_entry_display.render(self.derivation)
|
||||
|
||||
View.DispShowImage()
|
||||
|
||||
|
||||
def display_compact_seedqr_enabled(self) -> bool:
|
||||
lines = ["... [ Return to Settings ]"]
|
||||
lines.append("Enable")
|
||||
lines.append("Disable")
|
||||
|
||||
r = self.controller.menu_view.display_generic_selection_menu(lines, "Compact SeedQR")
|
||||
if r == 1:
|
||||
return None
|
||||
elif r == 2:
|
||||
return True
|
||||
elif r == 3:
|
||||
return False
|
||||
else:
|
||||
return None
|
||||
View.DispShowImage()
|
||||
@@ -94,8 +94,6 @@ class View:
|
||||
# 240x240 display with hardware SPI:
|
||||
View.bus = 0
|
||||
View.device = 0
|
||||
|
||||
# TODO: Add `disable_hardware` option for test suite here, too?
|
||||
View.disp = ST7789(SPI.SpiDev(View.bus, View.device),View.RST, View.DC, View.BL)
|
||||
View.disp.Init()
|
||||
|
||||
|
||||
@@ -12,5 +12,4 @@ camera_rotation = 0
|
||||
network = main
|
||||
software = Prompt
|
||||
qr_density = 2
|
||||
custom_derivation = m/0/0
|
||||
compact_seedqr_enabled = False
|
||||
custom_derivation = m/0/0
|
||||
@@ -2,7 +2,6 @@ import configparser
|
||||
import pytest
|
||||
from mock import MagicMock
|
||||
from seedsigner.controller import Controller
|
||||
from seedsigner.models.settings import Settings
|
||||
|
||||
|
||||
|
||||
@@ -18,13 +17,6 @@ def test_singleton_get_instance_without_configure_fails():
|
||||
|
||||
def test_singleton_get_instance_preserves_state():
|
||||
""" Changes to the Controller singleton should be preserved across calls to get_instance() """
|
||||
|
||||
# Must reset Singleton instances; pytest cannot properly isolate Singletons for us
|
||||
# automatically.
|
||||
# TODO: Cleaner solution here would be nice.
|
||||
Settings._instance = None
|
||||
Controller._instance = None
|
||||
|
||||
settings = """
|
||||
[system]
|
||||
debug = False
|
||||
@@ -33,21 +25,18 @@ def test_singleton_get_instance_preserves_state():
|
||||
|
||||
[display]
|
||||
text_color = ORANGE
|
||||
qr_background_color = FFFFFF
|
||||
camera_rotation = 0
|
||||
|
||||
[wallet]
|
||||
network = main
|
||||
software = Prompt
|
||||
qr_density = 2
|
||||
custom_derivation = m/0/0
|
||||
compact_seedqr_enabled = False
|
||||
"""
|
||||
config = configparser.ConfigParser()
|
||||
config.read_string(settings)
|
||||
|
||||
# Initialize the instance and verify that it read the config settings
|
||||
Controller.configure_instance(config, disable_hardware=True)
|
||||
Controller.configure_instance(config)
|
||||
controller = Controller.get_instance()
|
||||
assert controller.color == "ORANGE"
|
||||
|
||||
@@ -57,42 +46,3 @@ def test_singleton_get_instance_preserves_state():
|
||||
# ...get a new copy of the instance and confirm change
|
||||
controller = Controller.get_instance()
|
||||
assert controller.color == "purple"
|
||||
|
||||
|
||||
def test_missing_settings_get_defaults():
|
||||
""" Should gracefully handle any missing fields from `settings.ini` """
|
||||
# TODO: This is not complete; currently only handles missing compact_seedqr_enabled.
|
||||
|
||||
# Must reset Singleton instances; pytest cannot properly isolate Singletons for us
|
||||
# automatically.
|
||||
# TODO: Cleaner solution here would be nice.
|
||||
Settings._instance = None
|
||||
Controller._instance = None
|
||||
|
||||
# Intentionally omit `compact_seedqr_enabled` from settings:
|
||||
settings = """
|
||||
[system]
|
||||
debug = False
|
||||
default_language = en
|
||||
persistent_settings = False
|
||||
|
||||
[display]
|
||||
text_color = ORANGE
|
||||
qr_background_color = FFFFFF
|
||||
camera_rotation = 0
|
||||
|
||||
[wallet]
|
||||
network = main
|
||||
software = Prompt
|
||||
qr_density = 2
|
||||
custom_derivation = m/0/0
|
||||
"""
|
||||
config = configparser.ConfigParser()
|
||||
config.read_string(settings)
|
||||
|
||||
# Controller should parse the settings fine, even though a field is missing
|
||||
Controller.configure_instance(config, disable_hardware=True)
|
||||
|
||||
# Controller should still have a default value
|
||||
controller = Controller.get_instance()
|
||||
assert controller.settings.compact_seedqr_enabled is False
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_ur_qr_decode_multisig():
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
for i in qrcodes:
|
||||
d.add_data(i)
|
||||
d.addString(i)
|
||||
assert d.qrType() == QRType.PSBTUR2
|
||||
|
||||
#complete should be true
|
||||
@@ -63,7 +63,7 @@ def test_base64_single_frame_singlsig():
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
for i in qrcodes:
|
||||
d.add_data(i)
|
||||
d.addString(i)
|
||||
assert d.qrType() == QRType.PSBTBASE64
|
||||
|
||||
#complete should be true
|
||||
@@ -108,7 +108,7 @@ def test_ur2_sparrow_singlesig_to_self():
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
for i in qrcodes:
|
||||
d.add_data(i)
|
||||
d.addString(i)
|
||||
assert d.qrType() == QRType.PSBTUR2
|
||||
|
||||
#complete should be true
|
||||
@@ -127,7 +127,7 @@ def test_base64_2_input_p2wsh():
|
||||
base64_psbt = "cHNidP8BALICAAAAAq1DhxRK+mUH4T6uUNob8bUaZ7MP+44MW4+Y9bOxpjhZAAAAAAD9////aWclWQ+45HKrI07r878E2UrAupT2paT4QurbmtNjYNQBAAAAAP3///8CQEIPAAAAAAAiACCpkDPDhmIzPlkJrjw9A71xjbIUWf3VUB7ooFJhTVm04tjSIQEAAAAAIgAgjQKFDauIXsV5u23LBdYgOwX1FwGGrLiQfWzBtFKZ7dIAAAAATwEENYfPBD5i336AAAACQStJhNVJul7vHKbo83VdmuAW2m0WaXLKDlFANn7dUNoCNbhLMdw4Knz7Q7o6exdL6UFhQegW9nJb0SUStbLEpawUAgjLdzAAAIABAACAAAAAgAIAAIBPAQQ1h88EnbHQAIAAAAI/2Nc7x7iMpJNapTe/OJTV4oifqzQcYY9KV2+PGRjCdQJoww1WnSNqfcxXGyux0q1PqfmzUqgJNqKJCpmqI9t47BQmu4PEMAAAgAEAAIAAAACAAgAAgE8BBDWHzwS6wUg5gAAAAh1Pvr3ZZ+GvcUwJl9OPz2cLXOnTAcBEC7zDtqIOt3IcA1aOofNgUZFu0baQw54SqOcGA7KAvTDOXygfKRilU2OqFHPF2gowAACAAQAAgAAAAIACAACAAAEBK4CWmAAAAAAAIgAgiYAxcG7dnrEiZ4VHFVHOo18XCalvhZYuMqBr9n7HESQBBWlSIQJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8iEDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMchA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHU64iBgJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8hwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAABAAAAIgYDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMccAgjLdzAAAIABAACAAAAAgAIAAIAAAAAAAQAAACIGA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHHHPF2gowAACAAQAAgAAAAIACAACAAAAAAAEAAAAAAQErgJaYAAAAAAAiACAzd60wM9EFnPHSNbsSJfyipL8myVLVP2/vwzotVUSNxQEFaVIhAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiIQKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdCEDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgFTriIGAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiHAIIy3cwAACAAQAAgAAAAIACAACAAAAAAAAAAAAiBgKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdBwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIgYDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgEcc8XaCjAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAABAWlSIQKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/iEDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYhA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrU64iAgKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/hwCCMt3MAAAgAEAAIAAAACAAgAAgAEAAAAAAAAAIgIDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYcc8XaCjAAAIABAACAAAAAgAIAAIABAAAAAAAAACICA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrHCa7g8QwAACAAQAAgAAAAIACAACAAQAAAAAAAAAAAA=="
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(base64_psbt)
|
||||
d.addString(base64_psbt)
|
||||
assert d.qrType() == QRType.PSBTBASE64
|
||||
|
||||
tx = d.getPSBT()
|
||||
@@ -154,7 +154,7 @@ def test_base64_1_input_p2sh_p2wsh():
|
||||
base64_psbt = "cHNidP8BAHICAAAAAR30J629i3Y/R8woRpLQ9JUa31rKxyM+Ny4NEsme48GWAAAAAAD9////Atw5XQUAAAAAF6kUdSESczdYagEyToVUSXyT8VTNz+OHgJaYAAAAAAAWABTmav7/w4OOcfCiewfjsA7eaujhYAAAAABPAQQ1h88EPmLffoAAAAHdXEj2dn8EYJ+rRdXEYu5laq6lJI5Mp+3t63ckwty05QKrJBNPewhwQaGPYRif6+XaxozFXvTXn7pU24H6fRy1FxQCCMt3MAAAgAEAAIAAAACAAQAAgE8BBDWHzwSdsdAAgAAAAeeOv56oeaaFTrNonMKDHk1C8brbWGFvdlecVue+v0/RAn/g4yI3oYsyen7OOcT7caYl4Mn7nQbyonHcusUR+GhzFCa7g8QwAACAAQAAgAAAAIABAACATwEENYfPBLrBSDmAAAABpzrb4oeEh2NNy/w/fr3osfyZTx7AaGDPAcP+LqeR3bYC5ioqmXPuazp69HwimlvecLylm9BLuyl/VpPXqiVrl20Uc8XaCjAAAIABAACAAAAAgAEAAIAAAQEgAOH1BQAAAAAXqRSv3gkn8731qcPbSDu4TJOlJJZ/PocBBCIAIOeiFBX5x0vX6CacrAUVovrs1DDCcKJS5qptFS3sjpDpAQVpUiECZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMhA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtIQOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsFOuIgYCZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMcc8XaCjAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtHCa7g8QwAACAAQAAgAAAAIABAACAAAAAAAAAAAAiBgOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsBwCCMt3MAAAgAEAAIAAAACAAQAAgAAAAAAAAAAAAAEAIgAgZBwTq05RkpqKv6FV6LQjuM07Qv0/bYfWVc9NUQOFvwQBAWlSIQI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDCEC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EhA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UU64iAgI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDBwCCMt3MAAAgAEAAIAAAACAAQAAgAEAAAAAAAAAIgIC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EcJruDxDAAAIABAACAAAAAgAEAAIABAAAAAAAAACICA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UHHPF2gowAACAAQAAgAAAAIABAACAAQAAAAAAAAAAAA=="
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(base64_psbt)
|
||||
d.addString(base64_psbt)
|
||||
assert d.qrType() == QRType.PSBTBASE64
|
||||
|
||||
tx = d.getPSBT()
|
||||
@@ -208,7 +208,7 @@ def test_ur_legacy():
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
for i in qrcodes:
|
||||
if d.add_data(i) == DecodeQRStatus.COMPLETE:
|
||||
if d.addString(i) == DecodeQRStatus.COMPLETE:
|
||||
break
|
||||
assert d.qrType() == QRType.PSBTURLEGACY
|
||||
|
||||
@@ -242,7 +242,7 @@ def test_specter_multisig_animated_qr():
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
for i in qrcodes:
|
||||
if d.add_data(i) == DecodeQRStatus.COMPLETE:
|
||||
if d.addString(i) == DecodeQRStatus.COMPLETE:
|
||||
break
|
||||
assert d.qrType() == QRType.PSBTSPECTER
|
||||
|
||||
@@ -280,7 +280,7 @@ def test_specter_multisig_animated_qr():
|
||||
|
||||
d2 = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
for i in qrcodes2:
|
||||
if d2.add_data(i) == DecodeQRStatus.COMPLETE:
|
||||
if d2.addString(i) == DecodeQRStatus.COMPLETE:
|
||||
break
|
||||
assert d2.qrType() == QRType.PSBTSPECTER
|
||||
|
||||
@@ -313,22 +313,22 @@ def test_mnemonic_qr():
|
||||
mnemonic = "height demise useless trap grow lion found off key clown transfer enroll"
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
|
||||
assert d.add_data(mnemonic) == DecodeQRStatus.COMPLETE
|
||||
assert d.addString(mnemonic) == DecodeQRStatus.COMPLETE
|
||||
assert d.getSeedPhrase() == mnemonic.split()
|
||||
|
||||
bad_mnemonic = "height useless trap grow lion found off key clown transfer enroll"
|
||||
d2 = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
assert d2.add_data(bad_mnemonic) == DecodeQRStatus.INVALID
|
||||
assert d2.addString(bad_mnemonic) == DecodeQRStatus.INVALID
|
||||
|
||||
bad2_mnemonic = "words not in the list of bip39"
|
||||
d3 = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
assert d3.add_data(bad2_mnemonic) == DecodeQRStatus.INVALID
|
||||
assert d3.addString(bad2_mnemonic) == DecodeQRStatus.INVALID
|
||||
|
||||
def test_short_4_letter_mnemonic_qr():
|
||||
|
||||
short_nm = "heig demi usel trap grow lion foun off key clow tran enro"
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(short_nm)
|
||||
d.addString(short_nm)
|
||||
|
||||
assert d.isComplete() == True
|
||||
assert d.getSeedPhrase() == ["height", "demise", "useless", "trap", "grow", "lion", "found", "off", "key", "clown", "transfer", "enroll"]
|
||||
@@ -352,54 +352,54 @@ def test_bitcoin_address():
|
||||
main_bech32_address3 = "BITCOIN:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?junk"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(bad1)
|
||||
d.addString(bad1)
|
||||
|
||||
assert d.qrType() == QRType.INVALID
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(legacy_address1)
|
||||
d.addString(legacy_address1)
|
||||
|
||||
assert d.getAddress() == legacy_address1
|
||||
assert d.getAddressType() == "P2PKH-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(legacy_address2)
|
||||
d.addString(legacy_address2)
|
||||
|
||||
assert d.getAddress() == legacy_address2
|
||||
assert d.getAddressType() == "P2PKH-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(main_bech32_address)
|
||||
d.addString(main_bech32_address)
|
||||
|
||||
assert d.getAddress() == main_bech32_address
|
||||
assert d.getAddressType() == "Bech32-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(test_bech32_address)
|
||||
d.addString(test_bech32_address)
|
||||
|
||||
assert d.getAddress() == test_bech32_address
|
||||
assert d.getAddressType() == "Bech32-test"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(main_nested_segwit_address)
|
||||
d.addString(main_nested_segwit_address)
|
||||
|
||||
assert d.getAddress() == main_nested_segwit_address
|
||||
assert d.getAddressType() == "P2SH-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(test_nested_segwit_address)
|
||||
d.addString(test_nested_segwit_address)
|
||||
|
||||
assert d.getAddress() == test_nested_segwit_address
|
||||
assert d.getAddressType() == "P2SH-test"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(main_bech32_address2)
|
||||
d.addString(main_bech32_address2)
|
||||
|
||||
assert d.getAddress() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
|
||||
assert d.getAddressType() == "Bech32-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(main_bech32_address3)
|
||||
d.addString(main_bech32_address3)
|
||||
|
||||
assert d.getAddress() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
|
||||
assert d.getAddressType() == "Bech32-main"
|
||||
@@ -408,9 +408,9 @@ def test_seed_qr():
|
||||
seed = "121802020768124106400009195602431595117715840445"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(seed)
|
||||
d.addString(seed)
|
||||
|
||||
assert d.qrType() == QRType.SEEDQR
|
||||
assert d.qrType() == QRType.SEEDSSQR
|
||||
assert d.getSeedPhrase() == "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash".split()
|
||||
|
||||
def test_specter_wallet_json():
|
||||
@@ -422,9 +422,9 @@ def test_specter_wallet_json():
|
||||
]
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(parts[1])
|
||||
d.add_data(parts[2])
|
||||
d.add_data(parts[0])
|
||||
d.addString(parts[1])
|
||||
d.addString(parts[2])
|
||||
d.addString(parts[0])
|
||||
|
||||
assert d.qrType() == QRType.SPECTERWALLETQR
|
||||
assert d.isComplete() == True
|
||||
@@ -432,10 +432,10 @@ def test_specter_wallet_json():
|
||||
|
||||
def test_specter_wallet_json2():
|
||||
|
||||
part = '{"label": "Testnet Single Zone", "blockheight": 2090512, "descriptor": "wpkh([990a73ad/84h/1h/0h]tpubDDHQMDnFdan2GyHBsG32VW9qiygbhVizGRTjiS3H79M49FSvpsvLXqLgp1yC7r43dXVHozWavi2Fc4WHUpZmQYmzoQbit28qJhLjScbAQWU/0/*)#ujr0xunp","devices": [{"type": "seedsigner", "label": "Single Seed Zone Testnet"}]}'
|
||||
1_part = '{"label": "Testnet Single Zone", "blockheight": 2090512, "descriptor": "wpkh([990a73ad/84h/1h/0h]tpubDDHQMDnFdan2GyHBsG32VW9qiygbhVizGRTjiS3H79M49FSvpsvLXqLgp1yC7r43dXVHozWavi2Fc4WHUpZmQYmzoQbit28qJhLjScbAQWU/0/*)#ujr0xunp","devices": [{"type": "seedsigner", "label": "Single Seed Zone Testnet"}]}'
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d.add_data(part)
|
||||
d.addString(1_part)
|
||||
|
||||
assert d.qrType() == QRType.SPECTERWALLETQR
|
||||
assert d.isComplete() == True
|
||||
|
||||
@@ -60,7 +60,7 @@ def test_seedsigner_qr():
|
||||
|
||||
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
|
||||
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), qr_type=QRType.SEEDQR, wordlist=bip39.WORDLIST)
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), qr_type=QRType.SEEDSSQR, wordlist=bip39.WORDLIST)
|
||||
|
||||
print(e.nextPart())
|
||||
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import os
|
||||
import pyzbar
|
||||
from embit import bip39
|
||||
from seedsigner.helpers.qr import QR
|
||||
from seedsigner.helpers.ur2.bytewords import decode
|
||||
from seedsigner.models.decode_qr import DecodeQR, DecodeQRStatus
|
||||
from seedsigner.models.encode_qr import EncodeQR
|
||||
from seedsigner.models.qr_type import QRType
|
||||
|
||||
|
||||
def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type):
|
||||
""" Helper method to re-run multiple variations of the same encode/decode test """
|
||||
print(entropy)
|
||||
seed_phrase = bip39.mnemonic_from_bytes(entropy).split()
|
||||
print(seed_phrase)
|
||||
assert len(seed_phrase) == mnemonic_length
|
||||
|
||||
e = EncodeQR(seed_phrase=seed_phrase, qr_type=qr_type, wordlist=bip39.WORDLIST)
|
||||
data = e.nextPart()
|
||||
print(data)
|
||||
|
||||
qr = QR()
|
||||
image = qr.qrimage(
|
||||
data=data,
|
||||
width=240,
|
||||
height=240,
|
||||
border=3
|
||||
)
|
||||
|
||||
decoder = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
status = decoder.addImage(image)
|
||||
assert status == DecodeQRStatus.COMPLETE
|
||||
|
||||
decoded_seed_phrase = decoder.getSeedPhrase()
|
||||
print(decoded_seed_phrase)
|
||||
assert seed_phrase == decoded_seed_phrase
|
||||
|
||||
|
||||
|
||||
def test_standard_seedqr_encode_decode_():
|
||||
""" Should encode 24- and 12- word mnemonics to Standard SeedQR format and decode
|
||||
them back again to their original mnemonic seed phrase.
|
||||
"""
|
||||
# 24-word seed
|
||||
run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.SEEDQR)
|
||||
|
||||
# 12-word seed
|
||||
run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.SEEDQR)
|
||||
|
||||
|
||||
|
||||
def test_compact_seedqr_encode_decode():
|
||||
""" Should encode 24- and 12- word mnemonics to CompactSeedQR format and decode
|
||||
them back again to their original mnemonic seed phrase.
|
||||
"""
|
||||
# 24-word seed
|
||||
run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 12-word seed
|
||||
run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
|
||||
|
||||
def test_compact_seedqr_handles_null_bytes():
|
||||
""" Should properly encode a CompactSeedQR with null bytes (b'\x00') in the input
|
||||
entropy and decode it back to the original mnemonic seed.
|
||||
"""
|
||||
# 24-word seed, null bytes at the front
|
||||
entropy = b'\x00' + os.urandom(31)
|
||||
run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 24-word seed, null bytes in the middle
|
||||
entropy = os.urandom(10) + b'\x00' + os.urandom(21)
|
||||
run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 24-word seed, null bytes at the end
|
||||
entropy = os.urandom(31) + b'\x00'
|
||||
run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 24-word seed, multiple null bytes
|
||||
entropy = os.urandom(5) + b'\x00' + os.urandom(5) + b'\x00' + os.urandom(20)
|
||||
run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 24-word seed, multiple null bytes in a row
|
||||
entropy = os.urandom(10) + b'\x00\x00' + os.urandom(20)
|
||||
run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 12-word seed, null bytes at the beginning
|
||||
entropy = b'\x00' + os.urandom(15)
|
||||
run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 12-word seed, null bytes in the middle
|
||||
entropy = os.urandom(5) + b'\x00' + os.urandom(10)
|
||||
run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 12-word seed, null bytes at the end
|
||||
entropy = os.urandom(15) + b'\x00'
|
||||
run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 12-word seed, multiple null bytes
|
||||
entropy = os.urandom(5) + b'\x00' + os.urandom(5) + b'\x00' + os.urandom(4)
|
||||
run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR)
|
||||
|
||||
# 12-word seed, multiple null bytes in a row
|
||||
entropy = os.urandom(10) + b'\x00\x00' + os.urandom(4)
|
||||
run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR)
|
||||