Browse Source

Merge branch 'master' of github.com:adityapk00/zecpaperwallet

duke
Aditya Kulkarni 5 years ago
parent
commit
d83e43f489
  1. 73
      README.md
  2. 2
      cli/Cargo.toml
  3. 88
      cli/mkrelease.sh
  4. 66
      cli/src/main.rs
  5. 19
      docker/Dockerfile
  6. BIN
      docs/paperwallet.png
  7. 24
      lib/Cargo.toml
  8. 373
      lib/src/paper.rs
  9. 166
      lib/src/pdf.rs
  10. 16
      qtlib/Makefile
  11. 61
      qtlib/src/lib.rs
  12. 15
      qtlib/src/zecpaperrust.h
  13. 6
      ui/.gitignore
  14. 86
      ui/papersapling.pro
  15. 0
      ui/qtlib/.gitignore
  16. 2
      ui/qtlib/Cargo.toml
  17. 19
      ui/qtlib/Makefile
  18. 56
      ui/qtlib/src/lib.rs
  19. 2
      ui/qtlib/src/main.cpp
  20. 15
      ui/qtlib/src/zecpaperrust.h
  21. 59
      ui/src/ellidedlabel.cpp
  22. 30
      ui/src/ellidedlabel.h
  23. 11
      ui/src/main.cpp
  24. 134
      ui/src/mainwindow.cpp
  25. 29
      ui/src/mainwindow.h
  26. 163
      ui/src/mainwindow.ui
  27. 76
      ui/src/precompiled.h
  28. 41
      ui/src/qrcode/BitBuffer.cpp
  29. 52
      ui/src/qrcode/BitBuffer.hpp
  30. 620
      ui/src/qrcode/QrCode.cpp
  31. 351
      ui/src/qrcode/QrCode.hpp
  32. 225
      ui/src/qrcode/QrSegment.cpp
  33. 216
      ui/src/qrcode/QrSegment.hpp
  34. 66
      ui/src/qrcodelabel.cpp
  35. 25
      ui/src/qrcodelabel.h
  36. 196
      ui/src/ui_mainwindow.h
  37. 144
      ui/src/ui_wallet.h
  38. 158
      ui/src/wallet.ui

73
README.md

@ -1,2 +1,73 @@
# zecpaperwallet
Zcash Sapling Paper Wallet Generator
zecpaperwallet is a Zcash Sapling paper wallet generator that can run completely offline. You can run it on an air-gapped computer to generate your shielded z-addresses, which will allow you to keep your keys completely offline.
*Example:*
![Paper Wallet](docs/paperwallet.png?raw=true)
# Download
zecpaperwallet is available as pre-built binaries from our [release page](https://github.com/adityapk00/zecpaperwallet/releases). Download the zip file for your platform, extract it and run the `./zecpaperwallet` binary.
# Generating wallets
To generate a Zcash paper wallet, simply run `./zecpaperwallet`
You'll be asked to type some random characters that will add entropy to the random number generator. Run with `--help` to see all options
## Saving as PDFs
To generate a Zcash paper wallet and save it as a PDF, run
`./zecpaperwallet -z 3 --format pdf zecpaper-output.pdf`
This will generate 3 shielded z-addresses and their corresponding private keys, and save them in a PDF file called `zecpaper-output.pdf`
# Compiling from Source
zecpaperwallet is built with rust. To compile from source, you [install Rust](https://www.rust-lang.org/tools/install). Basically, you need to:
```
curl https://sh.rustup.rs -sSf | sh
```
Checkout the zecpaperwallet repository and build the CLI
```
git clone https://github.com/adityapk00/zecpaperwallet.git
cd zecpaperwallet/cli
cargo build --release
```
The binary is available in the `target/release` folder.
## Ensuring Security
When generating paper wallets that will store large amounts of crypto, please take special care to ensure the keys are generated and kept completely offline.
1. `zecpaperwallet` supports ARMv8 (Raspberry Pi 3+). You can put one in a Faraday cage along with a printer, and print out the PDFs securely.
2. Please ensure you supply random entropy when you run `zecpaperwallet`. Your entropy is mixed in with system-provided entropy to generate keys
3. If you can, run with `unshare`, which will disable all network interfaces to a process, providing you with an additional layer of safety. (See next section)
4. After you've generated the keys, you can tear off the Address potion of the wallet and take it to your online computer/phone to send the address funds. Please always keep the private key offline.
5. When you're ready to spend the cold storage keys, import the private key into a full node, then don't re-use the key again.
### Run without network
If you are running a newish version of Linux, you can be doubly sure that the process is not contacting the network by running zecpaperwallet without the network namespace.
```
sudo unshare -n ./target/release/zecpaperwallet
```
`unshare -n` runs the process without a network interface which means you can be sure that your data is not being sent across the network.
## Help options
```
USAGE:
zecpaperwallet [FLAGS] [OPTIONS] [output]
FLAGS:
-h, --help Prints help information
-n, --nohd Don't reuse HD keys. Normally, zecpaperwallet will use the same HD key to derive multiple
addresses. This flag will use a new seed for each address
--testnet Generate Testnet addresses
-V, --version Prints version information
OPTIONS:
-e, --entropy <entropy> Provide additional entropy to the random number generator. Any random string,
containing 32-64 characters
-f, --format <FORMAT> What format to generate the output in [default: json] [possible values: pdf, json]
-t, --taddrs <t_addresses> Numbe rof T addresses to generate [default: 0]
-z, --zaddrs <z_addresses> Number of Z addresses (Sapling) to generate [default: 1]
ARGS:
<output> Name of output file.
```

2
cli/Cargo.toml

@ -5,7 +5,7 @@ authors = ["ZecWallet"]
edition = "2018"
[dependencies]
clap = "~2.33"
clap = "2.33.0"
zecpaperlib = { path = "../lib" }
json = "0.11.14"
printpdf = "0.2.8"

88
cli/mkrelease.sh

@ -0,0 +1,88 @@
#!/bin/bash
# This script depends on a docker image already being built
# To build it,
# cd docker
# docker build --tag rust/zecpaperwallet:v0.1 .
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-v|--version)
APP_VERSION="$2"
shift # past argument
shift # past value
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
if [ -z $APP_VERSION ]; then echo "APP_VERSION is not set"; exit 1; fi
# Clean everything first
cargo clean
# Compile for mac directly
cargo build --release
# For Windows and Linux, build via docker
docker run --rm -v $(pwd)/..:/opt/zecpaperwallet rust/zecpaperwallet:v0.2 bash -c "cd /opt/zecpaperwallet/cli && cargo build --release --target x86_64-unknown-linux-musl && cargo build --release --target x86_64-pc-windows-gnu && cargo build --release --target aarch64-unknown-linux-gnu"
# Now sign and zip the binaries
#macOS
rm -rf target/macOS-zecpaperwallet-v$APP_VERSION
mkdir -p target/macOS-zecpaperwallet-v$APP_VERSION
cp target/release/zecpaperwallet target/macOS-zecpaperwallet-v$APP_VERSION/
gpg --batch --output target/macOS-zecpaperwallet-v$APP_VERSION/zecpaperwallet.sig --detach-sig target/macOS-zecpaperwallet-v$APP_VERSION/zecpaperwallet
cd target
cd macOS-zecpaperwallet-v$APP_VERSION
gsha256sum zecpaperwallet > sha256sum.txt
cd ..
zip -r macOS-zecpaperwallet-v$APP_VERSION.zip macOS-zecpaperwallet-v$APP_VERSION
cd ..
#Linux
rm -rf target/linux-zecpaperwallet-v$APP_VERSION
mkdir -p target/linux-zecpaperwallet-v$APP_VERSION
cp target/x86_64-unknown-linux-musl/release/zecpaperwallet target/linux-zecpaperwallet-v$APP_VERSION/
gpg --batch --output target/linux-zecpaperwallet-v$APP_VERSION/zecpaperwallet.sig --detach-sig target/linux-zecpaperwallet-v$APP_VERSION/zecpaperwallet
cd target
cd linux-zecpaperwallet-v$APP_VERSION
gsha256sum zecpaperwallet > sha256sum.txt
cd ..
zip -r linux-zecpaperwallet-v$APP_VERSION.zip linux-zecpaperwallet-v$APP_VERSION
cd ..
#Windows
rm -rf target/Windows-zecpaperwallet-v$APP_VERSION
mkdir -p target/Windows-zecpaperwallet-v$APP_VERSION
cp target/x86_64-pc-windows-gnu/release/zecpaperwallet.exe target/Windows-zecpaperwallet-v$APP_VERSION/
gpg --batch --output target/Windows-zecpaperwallet-v$APP_VERSION/zecpaperwallet.sig --detach-sig target/Windows-zecpaperwallet-v$APP_VERSION/zecpaperwallet.exe
cd target
cd Windows-zecpaperwallet-v$APP_VERSION
gsha256sum zecpaperwallet.exe > sha256sum.txt
cd ..
zip -r Windows-zecpaperwallet-v$APP_VERSION.zip Windows-zecpaperwallet-v$APP_VERSION
cd ..
# aarch64 (armv8)
rm -rf target/aarch64-zecpaperwallet-v$APP_VERSION
mkdir -p target/aarch64-zecpaperwallet-v$APP_VERSION
cp target/aarch64-unknown-linux-gnu/release/zecpaperwallet target/aarch64-zecpaperwallet-v$APP_VERSION/
gpg --batch --output target/aarch64-zecpaperwallet-v$APP_VERSION/zecpaperwallet.sig --detach-sig target/aarch64-zecpaperwallet-v$APP_VERSION/zecpaperwallet
cd target
cd aarch64-zecpaperwallet-v$APP_VERSION
gsha256sum zecpaperwallet > sha256sum.txt
cd ..
zip -r aarch64-zecpaperwallet-v$APP_VERSION.zip aarch64-zecpaperwallet-v$APP_VERSION
cd ..

66
cli/src/main.rs

@ -12,7 +12,6 @@ fn main() {
.version("1.0")
.about("A command line Zcash Sapling paper wallet generator")
.arg(Arg::with_name("testnet")
.short("t")
.long("testnet")
.help("Generate Testnet addresses"))
.arg(Arg::with_name("format")
@ -32,10 +31,25 @@ fn main() {
.long("output")
.index(1)
.help("Name of output file."))
.arg(Arg::with_name("entropy")
.short("e")
.long("entropy")
.takes_value(true)
.help("Provide additional entropy to the random number generator. Any random string, containing 32-64 characters"))
.arg(Arg::with_name("t_addresses")
.short("t")
.long("taddrs")
.help("Numbe rof T addresses to generate")
.takes_value(true)
.default_value("0")
.validator(|i:String| match i.parse::<i32>() {
Ok(_) => return Ok(()),
Err(_) => return Err(format!("Number of addresses '{}' is not a number", i))
}))
.arg(Arg::with_name("z_addresses")
.short("z")
.long("z_addresses")
.help("Number of Z addresses (sapling) to generate")
.long("zaddrs")
.help("Number of Z addresses (Sapling) to generate")
.takes_value(true)
.default_value("1")
.validator(|i:String| match i.parse::<i32>() {
@ -48,6 +62,7 @@ fn main() {
let nohd: bool = matches.is_present("nohd");
// Get the filename and output format
let filename = matches.value_of("output");
let format = matches.value_of("format").unwrap();
@ -57,11 +72,41 @@ fn main() {
return;
}
let num_addresses = matches.value_of("z_addresses").unwrap().parse::<u32>().unwrap();
// Get user entropy.
let mut entropy: Vec<u8> = Vec::new();
// If the user hasn't specified any, read from the stdin
if matches.value_of("entropy").is_none() {
// Read from stdin
println!("Provide additional entropy for generating random numbers. Type in a string of random characters, press [ENTER] when done");
let mut buffer = String::new();
let stdin = io::stdin();
stdin.lock().read_line(&mut buffer).unwrap();
entropy.extend_from_slice(buffer.as_bytes());
} else {
// Use provided entropy.
entropy.extend(matches.value_of("entropy").unwrap().as_bytes());
}
// Get the filename and output format
let filename = matches.value_of("output");
let format = matches.value_of("format").unwrap();
// Writing to PDF requires a filename
if format == "pdf" && filename.is_none() {
eprintln!("Need an output file name when writing to PDF");
return;
}
// Number of t addresses to generate
let t_addresses = matches.value_of("t_addresses").unwrap().parse::<u32>().unwrap();
// Number of z addresses to generate
let z_addresses = matches.value_of("z_addresses").unwrap().parse::<u32>().unwrap();
print!("Generating {} Sapling addresses.........", num_addresses);
print!("Generating {} Sapling addresses and {} Transparent addresses...", z_addresses, t_addresses);
io::stdout().flush().ok();
let addresses = generate_wallet(testnet, nohd, num_addresses);
let addresses = generate_wallet(testnet, nohd, z_addresses, t_addresses, &entropy);
println!("[OK]");
// If the default format is present, write to the console if the filename is absent
@ -76,7 +121,12 @@ fn main() {
// We already know the output file name was specified
print!("Writing {:?} as a PDF file...", filename.unwrap());
io::stdout().flush().ok();
pdf::save_to_pdf(&addresses, filename.unwrap());
println!("[OK]");
match pdf::save_to_pdf(&addresses, filename.unwrap()) {
Ok(_) => { println!("[OK]");},
Err(e) => {
eprintln!("[ERROR]");
eprintln!("{}", e);
}
};
}
}

19
docker/Dockerfile

@ -0,0 +1,19 @@
FROM rust:1.34
LABEL Description="Rust compile env for Linux + Windows (cross)"
RUN apt update
RUN apt install -y build-essential mingw-w64 gcc-aarch64-linux-gnu
RUN rustup target add x86_64-pc-windows-gnu
RUN rustup target add x86_64-unknown-linux-musl
RUN rustup target add aarch64-unknown-linux-gnu
# Append the linker to the cargo config for Windows cross compile
RUN echo "[target.x86_64-pc-windows-gnu]" >> /usr/local/cargo/config && \
echo "linker = '/usr/bin/x86_64-w64-mingw32-gcc'" >> /usr/local/cargo/config
RUN echo "[target.aarch64-unknown-linux-gnu]" >> /usr/local/cargo/config && \
echo "linker = '/usr/bin/aarch64-linux-gnu-gcc'" >> /usr/local/cargo/config
ENV CC_x86_64_unknown_linux_musl="gcc"
ENV CC_aarch64_unknown_linux_gnu="aarch64-linux-gnu-gcc"

BIN
docs/paperwallet.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

24
lib/Cargo.toml

@ -5,19 +5,17 @@ authors = ["ZecWallet"]
edition = "2018"
[dependencies]
bech32 = "0.6"
bellman = { git = "https://github.com/zcash/librustzcash" }
ff = { git = "https://github.com/zcash/librustzcash" }
hex = "0.3"
pairing = { git = "https://github.com/zcash/librustzcash" }
protobuf = "2"
rand = "0.5"
rusqlite = { version = "0.15", features = ["bundled"], optional = true }
sapling-crypto = { git = "https://github.com/zcash/librustzcash" }
time = { version = "0.1", optional = true }
zcash_primitives = { git = "https://github.com/zcash/librustzcash" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash" }
zip32 = { git = "https://github.com/zcash/librustzcash" }
hex = "0.3"
bech32 = "0.6"
zip32 = { git = "https://github.com/zcash/librustzcash", rev="3b6f5e3d5ede6469f6ae85357f0b03d4c1b45cfe" }
json = "0.11.14"
qrcode = { version = "0.8", default-features = false }
printpdf = "0.2.8"
printpdf = "0.2.8"
secp256k1 = { version = "0.13.0", features = ["rand"] }
ripemd160 = "0.8.0"
sha2 = "0.8.0"
base58 = "0.1.0"
[dev-dependencies]
array2d = "0.1.0"

373
lib/src/paper.rs

@ -1,25 +1,104 @@
use hex;
use secp256k1;
use ripemd160::{Ripemd160, Digest};
use base58::{ToBase58};
use zip32::{ChildIndex, ExtendedSpendingKey};
use bech32::{Bech32, u5, ToBase32};
use rand::{Rng, ChaChaRng, FromEntropy};
use rand::{Rng, ChaChaRng, FromEntropy, SeedableRng};
use json::{array, object};
use sha2;
/// A trait for converting a [u8] to base58 encoded string.
pub trait ToBase58Check {
/// Converts a value of `self` to a base58 value, returning the owned string.
/// The version is a coin-specific prefix that is added.
/// The suffix is any bytes that we want to add at the end (like the "iscompressed" flag for
/// Secret key encoding)
fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String;
}
impl ToBase58Check for [u8] {
fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String {
let mut payload: Vec<u8> = Vec::new();
payload.extend_from_slice(version);
payload.extend_from_slice(self);
payload.extend_from_slice(suffix);
let mut checksum = double_sha256(&payload);
payload.append(&mut checksum[..4].to_vec());
payload.to_base58()
}
}
/// Sha256(Sha256(value))
fn double_sha256(payload: &[u8]) -> Vec<u8> {
let h1 = sha2::Sha256::digest(&payload);
let h2 = sha2::Sha256::digest(&h1);
h2.to_vec()
}
/// Parameters used to generate addresses and private keys. Look in chainparams.cpp (in zcashd/src)
/// to get these values.
/// Usually these will be different for testnet and for mainnet.
struct CoinParams {
taddress_version: [u8; 2],
tsecret_prefix : [u8; 1],
zaddress_prefix : String,
zsecret_prefix : String,
cointype : u32,
}
fn params(testnet: bool) -> CoinParams {
if testnet {
CoinParams {
taddress_version : [0x1D, 0x25],
tsecret_prefix : [0xEF],
zaddress_prefix : "ztestsapling".to_string(),
zsecret_prefix : "secret-extended-key-test".to_string(),
cointype : 1
}
} else {
CoinParams {
taddress_version : [0x1C, 0xB8],
tsecret_prefix : [0x80],
zaddress_prefix : "zs".to_string(),
zsecret_prefix : "secret-extended-key-main".to_string(),
cointype : 133
}
}
}
/// Generate a series of `count` addresses and private keys.
pub fn generate_wallet(testnet: bool, nohd: bool, zcount: u32, tcount: u32, user_entropy: &[u8]) -> String {
// Get 32 bytes of system entropy
let mut system_entropy:[u8; 32] = [0; 32];
{
let mut system_rng = ChaChaRng::from_entropy();
system_rng.fill(&mut system_entropy);
}
// Add in user entropy to the system entropy, and produce a 32 byte hash...
let mut state = sha2::Sha256::new();
state.input(&system_entropy);
state.input(&user_entropy);
let mut final_entropy: [u8; 32] = [0; 32];
final_entropy.clone_from_slice(&double_sha256(&state.result()[..]));
// ...which will we use to seed the RNG
let mut rng = ChaChaRng::from_seed(final_entropy);
/**
* Generate a series of `count` addresses and private keys.
*/
pub fn generate_wallet(testnet: bool, nohd: bool, count: u32) -> String {
if !nohd {
// Allow HD addresses, so use only 1 seed
let mut rng = ChaChaRng::from_entropy();
let mut seed:[u8; 32] = [0; 32];
// Allow HD addresses, so use only 1 seed
let mut seed: [u8; 32] = [0; 32];
rng.fill(&mut seed);
return gen_addresses_with_seed_as_json(testnet, count, |i| (seed.to_vec(), i));
return gen_addresses_with_seed_as_json(testnet, zcount, tcount, |i| (seed.to_vec(), i));
} else {
// Not using HD addresses, so derive a new seed every time
return gen_addresses_with_seed_as_json(testnet, count, |_| {
let mut rng = ChaChaRng::from_entropy();
// Not using HD addresses, so derive a new seed every time
return gen_addresses_with_seed_as_json(testnet, zcount, tcount, |_| {
let mut seed:[u8; 32] = [0; 32];
rng.fill(&mut seed);
@ -28,53 +107,87 @@ pub fn generate_wallet(testnet: bool, nohd: bool, count: u32) -> String {
}
}
/**
* Generate `count` addresses with the given seed. The addresses are derived from m/32'/cointype'/index' where
* index is 0..count
*
* Note that cointype is 1 for testnet and 133 for mainnet
*
* get_seed is a closure that will take the address number being derived, and return a tuple cointaining the
* seed and child number to use to derive this wallet.
*
* It is useful if we want to reuse (or not) the seed across multiple wallets.
*/
fn gen_addresses_with_seed_as_json<F>(testnet: bool, count: u32, get_seed: F) -> String
where F: Fn(u32) -> (Vec<u8>, u32)
/// Generate `count` addresses with the given seed. The addresses are derived from m/32'/cointype'/index' where
/// index is 0..count
///
/// Note that cointype is 1 for testnet and 133 for mainnet
///
/// get_seed is a closure that will take the address number being derived, and return a tuple cointaining the
/// seed and child number to use to derive this wallet.
/// It is useful if we want to reuse (or not) the seed across multiple wallets.
fn gen_addresses_with_seed_as_json<F>(testnet: bool, zcount: u32, tcount: u32, mut get_seed: F) -> String
where F: FnMut(u32) -> (Vec<u8>, u32)
{
let mut ans = array![];
for i in 0..count {
// Note that for t-addresses, we don't use HD addresses
let (seed, _) = get_seed(0);
let mut rng_seed: [u8; 32] = [0; 32];
rng_seed.clone_from_slice(&seed[0..32]);
// derive a RNG from the seed
let mut rng = ChaChaRng::from_seed(rng_seed);
// First generate the Z addresses
for i in 0..zcount {
let (seed, child) = get_seed(i);
let (addr, pk, path) = get_address(testnet, &seed, child);
let (addr, pk, path) = get_zaddress(testnet, &seed, child);
ans.push(object!{
"num" => i,
"address" => addr,
"private_key" => pk,
"type" => "zaddr",
"seed" => path
}).unwrap();
}
// Next generate the T addresses
for i in 0..tcount {
let (addr, pk_wif) = get_taddress(testnet, &mut rng);
ans.push(object!{
"num" => i,
"address" => addr,
"private_key" => pk_wif,
"type" => "taddr"
}).unwrap();
}
return json::stringify_pretty(ans, 2);
}
// Generate a standard ZIP-32 address from the given seed at 32'/44'/0'/index
fn get_address(testnet: bool, seed: &[u8], index: u32) -> (String, String, json::JsonValue) {
let addr_prefix = if testnet {"ztestsapling"} else {"zs"};
let pk_prefix = if testnet {"secret-extended-key-test"} else {"secret-extended-key-main"};
let cointype = if testnet {1} else {133};
let spk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
/// Generate a t address
fn get_taddress(testnet: bool, mut rng: &mut ChaChaRng) -> (String, String) {
// SECP256k1 context
let ctx = secp256k1::Secp256k1::default();
let (sk, pubkey) = ctx.generate_keypair(&mut rng);
// Address
let mut hash160 = Ripemd160::new();
hash160.input(sha2::Sha256::digest(&pubkey.serialize().to_vec()));
let addr = hash160.result().to_base58check(&params(testnet).taddress_version, &[]);
// Private Key
let sk_bytes: &[u8] = &sk[..];
let pk_wif = sk_bytes.to_base58check(&params(testnet).tsecret_prefix, &[0x01]);
return (addr, pk_wif);
}
/// Generate a standard ZIP-32 address from the given seed at 32'/44'/0'/index
fn get_zaddress(testnet: bool, seed: &[u8], index: u32) -> (String, String, json::JsonValue) {
let spk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
&ExtendedSpendingKey::master(seed),
&[
ChildIndex::Hardened(32),
ChildIndex::Hardened(cointype),
ChildIndex::Hardened(params(testnet).cointype),
ChildIndex::Hardened(index)
],
);
let path = object!{
"HDSeed" => hex::encode(seed),
"path" => format!("m/32'/{}'/{}'", cointype, index)
"path" => format!("m/32'/{}'/{}'", params(testnet).cointype, index)
};
let (_d, addr) = spk.default_address().expect("Cannot get result");
@ -84,13 +197,13 @@ fn get_address(testnet: bool, seed: &[u8], index: u32) -> (String, String, json:
v.get_mut(..11).unwrap().copy_from_slice(&addr.diversifier.0);
addr.pk_d.write(v.get_mut(11..).unwrap()).expect("Cannot write!");
let checked_data: Vec<u5> = v.to_base32();
let encoded = Bech32::new(addr_prefix.into(), checked_data).expect("bech32 failed").to_string();
let encoded = Bech32::new(params(testnet).zaddress_prefix.into(), checked_data).expect("bech32 failed").to_string();
// Private Key is encoded as bech32 string
let mut vp = Vec::new();
spk.write(&mut vp).expect("Can't write private key");
let c_d: Vec<u5> = vp.to_base32();
let encoded_pk = Bech32::new(pk_prefix.into(), c_d).expect("bech32 failed").to_string();
let encoded_pk = Bech32::new(params(testnet).zsecret_prefix.into(), c_d).expect("bech32 failed").to_string();
return (encoded.to_string(), encoded_pk.to_string(), path);
}
@ -104,16 +217,14 @@ fn get_address(testnet: bool, seed: &[u8], index: u32) -> (String, String, json:
#[cfg(test)]
mod tests {
/**
* Test the wallet generation and that it is generating the right number and type of addresses
*/
/// Test the wallet generation and that it is generating the right number and type of addresses
#[test]
fn test_wallet_generation() {
use crate::paper::generate_wallet;
use std::collections::HashSet;
// Testnet wallet
let w = generate_wallet(true, false, 1);
let w = generate_wallet(true, false, 1, 0, &[]);
let j = json::parse(&w).unwrap();
assert_eq!(j.len(), 1);
assert!(j[0]["address"].as_str().unwrap().starts_with("ztestsapling"));
@ -122,7 +233,7 @@ mod tests {
// Mainnet wallet
let w = generate_wallet(false, false, 1);
let w = generate_wallet(false, false, 1, 0, &[]);
let j = json::parse(&w).unwrap();
assert_eq!(j.len(), 1);
assert!(j[0]["address"].as_str().unwrap().starts_with("zs"));
@ -130,7 +241,7 @@ mod tests {
assert_eq!(j[0]["seed"]["path"].as_str().unwrap(), "m/32'/133'/0'");
// Check if all the addresses are the same
let w = generate_wallet(true, false, 3);
let w = generate_wallet(true, false, 3, 0, &[]);
let j = json::parse(&w).unwrap();
assert_eq!(j.len(), 3);
@ -152,16 +263,61 @@ mod tests {
assert_eq!(set2.len(), 1);
}
/**
* Test nohd address generation, which does not use the same sed.
*/
#[test]
fn test_tandz_wallet_generation() {
use crate::paper::generate_wallet;
use std::collections::HashSet;
// Testnet wallet
let w = generate_wallet(true, false, 1, 1, &[]);
let j = json::parse(&w).unwrap();
assert_eq!(j.len(), 2);
assert!(j[0]["address"].as_str().unwrap().starts_with("ztestsapling"));
assert!(j[0]["private_key"].as_str().unwrap().starts_with("secret-extended-key-test"));
assert_eq!(j[0]["seed"]["path"].as_str().unwrap(), "m/32'/1'/0'");
assert!(j[1]["address"].as_str().unwrap().starts_with("tm"));
let pk = j[1]["private_key"].as_str().unwrap();
assert!(pk.starts_with("c") || pk.starts_with("9"));
// Mainnet wallet
let w = generate_wallet(false, false, 1, 1, &[]);
let j = json::parse(&w).unwrap();
assert_eq!(j.len(), 2);
assert!(j[0]["address"].as_str().unwrap().starts_with("zs"));
assert!(j[0]["private_key"].as_str().unwrap().starts_with("secret-extended-key-main"));
assert_eq!(j[0]["seed"]["path"].as_str().unwrap(), "m/32'/133'/0'");
assert!(j[1]["address"].as_str().unwrap().starts_with("t1"));
let pk = j[1]["private_key"].as_str().unwrap();
assert!(pk.starts_with("L") || pk.starts_with("K") || pk.starts_with("5"));
// Check if all the addresses are the same
let w = generate_wallet(true, false, 3, 3, &[]);
let j = json::parse(&w).unwrap();
assert_eq!(j.len(), 6);
let mut set1 = HashSet::new();
for i in 0..6 {
set1.insert(j[i]["address"].as_str().unwrap());
set1.insert(j[i]["private_key"].as_str().unwrap());
}
// There should be 6 + 6 distinct addresses and private keys
assert_eq!(set1.len(), 12);
}
/// Test nohd address generation, which does not use the same sed.
#[test]
fn test_nohd() {
use crate::paper::generate_wallet;
use std::collections::HashSet;
// Check if all the addresses use a different seed
let w = generate_wallet(true, true, 3);
let w = generate_wallet(true, true, 3, 0, &[]);
let j = json::parse(&w).unwrap();
assert_eq!(j.len(), 3);
@ -183,7 +339,7 @@ mod tests {
assert_eq!(set2.len(), 3);
}
// Test the address derivation against the test data (see below)
/// Test the address derivation against the test data (see below)
fn test_address_derivation(testdata: &str, testnet: bool) {
use crate::paper::gen_addresses_with_seed_as_json;
let td = json::parse(&testdata.replace("'", "\"")).unwrap();
@ -192,7 +348,7 @@ mod tests {
let seed = hex::decode(i["seed"].as_str().unwrap()).unwrap();
let num = i["num"].as_u32().unwrap();
let addresses = gen_addresses_with_seed_as_json(testnet, num+1, |child| (seed.clone(), child));
let addresses = gen_addresses_with_seed_as_json(testnet, num+1, 0, |child| (seed.clone(), child));
let j = json::parse(&addresses).unwrap();
assert_eq!(j[num as usize]["address"], i["addr"]);
@ -200,44 +356,89 @@ mod tests {
}
}
/*
Test data was derived from zcashd. It cointains 20 sets of seeds, and for each seed, it contains 5 accounts that are derived for the testnet and mainnet.
We'll use the same seed and derive the same set of addresses here, and then make sure that both the address and private key matches up.
To derive the test data, add something like this in test_wallet.cpp and run with
./src/zcash-gtest --gtest_filter=WalletTests.*
```
void print_wallet(std::string seed, std::string pk, std::string addr, int num) {
std::cout << "{'seed': '" << seed << "', 'pk': '" << pk << "', 'addr': '" << addr << "', 'num': " << num << "}," << std::endl;
}
void gen_addresses() {
for (int i=0; i < 20; i++) {
HDSeed seed = HDSeed::Random();
for (int j=0; j < 5; j++) {
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto xsk = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT)
.Derive(Params().BIP44CoinType() | ZIP32_HARDENED_KEY_LIMIT)
.Derive(j | ZIP32_HARDENED_KEY_LIMIT);
auto rawSeed = seed.RawSeed();
print_wallet(HexStr(rawSeed.begin(), rawSeed.end()),
EncodeSpendingKey(xsk), EncodePaymentAddress(xsk.DefaultAddress()), j);
}
}
}
#[test]
fn test_taddr_testnet() {
use crate::paper::get_taddress;
use rand::{ChaChaRng, SeedableRng};
// 0-seeded, for predictable outcomes
let seed : [u8; 32] = [0; 32];
let mut rng = ChaChaRng::from_seed(seed);
let testdata = [
["tmEw65eREGVhneyqwB442UnjeVaTaJVWvi9", "cRZUuqfYFZ6bv7QxEjDMHpnxQmJG2oncZ2DAZsfVXmB2SCts8Z2N"],
["tmXJQzrFTRAPpmVhrWTVUwFp7X4sisUdw2X", "cUtxiJ8n67Au9eM7WnTyRQNewfcW9bJZkKWkUkKgwqdsp2eayU57"],
["tmGb1FcP31uFVtKU319thMiR2J7krABDWku", "cSuqVYsMGutnxjYNeL1DMQpiv2isMwF8gVG2oLNTnECWVGjTpB5N"],
["tmHQ9fDGWqk684tjWvvEWZ8BSkpNrQ162Yb", "cNynpdfzR4jgZi5E6ihAQhzeKB2w7NXNbVvznr9oW26VoJCGHiLW"],
["tmNS3LoTEFgUuEwzyYinoan4AceJ4dc21SR", "cP6FPTWbehuiXBpUnDW5iYVayEKeboxFQftx97GfSGwBs1HgPYjS"]
];
for i in 0..5 {
let (a, sk) = get_taddress(true, &mut rng);
assert_eq!(a, testdata[i][0]);
assert_eq!(sk, testdata[i][1]);
}
}
TEST(WalletTests, SaplingAddressTest) {
SelectParams(CBaseChainParams::TESTNET);
gen_addresses();
SelectParams(CBaseChainParams::MAIN);
gen_addresses();
#[test]
fn test_taddr_mainnet() {
use crate::paper::get_taddress;
use rand::{ChaChaRng, SeedableRng};
// 0-seeded, for predictable outcomes
let seed : [u8; 32] = [0; 32];
let mut rng = ChaChaRng::from_seed(seed);
let testdata = [
["t1P6LkovpsqCHWjeVWKkHd84ttbNksfW6k6", "L1CVSvfgpVQLkfwgrKQDvWHtnXzrNMgvUz4hTTCz2eX2BTmWSCaE"],
["t1fTfg1m42VtKdFWQqjBk5b9Mv5nuPo7XLL", "L4XyFP8vf3UdzCsr8Ner45sbKSK6V9CsgHNHNKsBSiysZHaeQDq7"],
["t1QkFvmtddEjzk5GbLRaxW3kGh8g2jDqSHP", "L2Yr2dsVqrCXoJ57FvC5z6KfHoRThV9ScT7ZguuxH7YWEXboHTY6"],
["t1RZQLNn7T5acveY5GBvmhTWh9qJ2vy9hC9", "KxcoMig8z13RQGbxiJt33PVagwjXSvRgXTnXgRhHzuSVYZ9KdGUh"],
["t1WbJ1xxps1yQ6hoXszV4j7PR1fDF7WogPz", "KxjFvYWkDeDTMkMDPogxMDzXM12EwMrZLdkV2gp9wAHBcGEcBPqZ"],
];
for i in 0..5 {
let (a, sk) = get_taddress(false, &mut rng);
assert_eq!(a, testdata[i][0]);
assert_eq!(sk, testdata[i][1]);
}
```
*/
}
/// Test data was derived from zcashd. It cointains 20 sets of seeds, and for each seed, it contains 5 accounts that are derived for the testnet and mainnet.
/// We'll use the same seed and derive the same set of addresses here, and then make sure that both the address and private key matches up.
/// To derive the test data, add something like this in test_wallet.cpp and run with
/// ./src/zcash-gtest --gtest_filter=WalletTests.*
///
/// ```
/// void print_wallet(std::string seed, std::string pk, std::string addr, int num) {
/// std::cout << "{'seed': '" << seed << "', 'pk': '" << pk << "', 'addr': '" << addr << "', 'num': " << num << "}," << std::endl;
/// }
///
/// void gen_addresses() {
/// for (int i=0; i < 20; i++) {
/// HDSeed seed = HDSeed::Random();
/// for (int j=0; j < 5; j++) {
/// auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
/// auto xsk = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT)
/// .Derive(Params().BIP44CoinType() | ZIP32_HARDENED_KEY_LIMIT)
/// .Derive(j | ZIP32_HARDENED_KEY_LIMIT);
/// auto rawSeed = seed.RawSeed();
/// print_wallet(HexStr(rawSeed.begin(), rawSeed.end()),
/// EncodeSpendingKey(xsk), EncodePaymentAddress(xsk.DefaultAddress()), j);
/// }
/// }
/// }
///
/// TEST(WalletTests, SaplingAddressTest) {
/// SelectParams(CBaseChainParams::TESTNET);
/// gen_addresses();
///
/// SelectParams(CBaseChainParams::MAIN);
/// gen_addresses();
/// }
/// ```
#[test]
fn test_address_derivation_testnet() {
let testdata = "[

166
lib/src/pdf.rs

@ -12,7 +12,7 @@ use printpdf::*;
/**
* Save the list of wallets (address + private keys) to the given PDF file name.
*/
pub fn save_to_pdf(addresses: &str, filename: &str) {
pub fn save_to_pdf(addresses: &str, filename: &str) -> Result<(), String> {
let (doc, page1, layer1) = PdfDocument::new("Zec Sapling Paper Wallet", Mm(210.0), Mm(297.0), "Layer 1");
let font = doc.add_builtin_font(BuiltinFont::Courier).unwrap();
@ -39,11 +39,19 @@ pub fn save_to_pdf(addresses: &str, filename: &str) {
current_layer = doc.get_page(page2).add_layer("Layer 3");
}
// Add address + private key
add_address_to_page(&current_layer, &font, &font_bold, kv["address"].as_str().unwrap(), pos);
add_pk_to_page(&current_layer, &font, &font_bold, kv["private_key"].as_str().unwrap(), kv["seed"]["HDSeed"].as_str().unwrap(), kv["seed"]["path"].as_str().unwrap(), pos);
let address = kv["address"].as_str().unwrap();
let pk = kv["private_key"].as_str().unwrap();
// Is the shape stroked? Is the shape closed? Is the shape filled?
let (seed, hdpath, is_taddr) = if kv["type"].as_str().unwrap() == "zaddr" {
(kv["seed"]["HDSeed"].as_str().unwrap(), kv["seed"]["path"].as_str().unwrap(), false)
} else {
("", "", true)
};
// Add address + private key
add_address_to_page(&current_layer, &font, &font_bold, address, is_taddr, pos);
add_pk_to_page(&current_layer, &font, &font_bold, pk, address, is_taddr, seed, hdpath, pos);
let line1 = Line {
points: vec![(Point::new(Mm(5.0), Mm(160.0)), false), (Point::new(Mm(205.0), Mm(160.0)), false)],
is_closed: true,
@ -70,7 +78,21 @@ pub fn save_to_pdf(addresses: &str, filename: &str) {
pos = pos + 1;
};
doc.save(&mut BufWriter::new(File::create(filename).unwrap())).unwrap();
let file = match File::create(filename) {
Ok(f) => f,
Err(e) => {
return Err(format!("Couldn't open {} for writing. Aborting. {}", filename, e));
}
};
match doc.save(&mut BufWriter::new(file)) {
Ok(_) => (),
Err(e) => {
return Err(format!("Couldn't save {}. Aborting. {}", filename, e));
}
};
return Ok(());
}
/**
@ -112,45 +134,83 @@ fn add_footer_to_page(current_layer: &PdfLayerReference, font: &IndirectFontRef,
/**
* Add the address section to the PDF at `pos`. Note that each page can fit only 2 wallets, so pos has to effectively be either 0 or 1.
*/
fn add_address_to_page(current_layer: &PdfLayerReference, font: &IndirectFontRef, font_bold: &IndirectFontRef, address: &str, pos: u32) {
let (scaledimg, finalsize) = qrcode_scaled(address, 10);
fn add_address_to_page(current_layer: &PdfLayerReference, font: &IndirectFontRef, font_bold: &IndirectFontRef, address: &str, is_taddr: bool, pos: u32) {
let (scaledimg, finalsize) = qrcode_scaled(address, if is_taddr {13} else {10});
// page_height top_margin vertical_padding position
let ypos = 297.0 - 5.0 - 35.0 - (140.0 * pos as f64);
let title = if is_taddr {"T Address"} else {"ZEC Address (Sapling)"};
// page_height top_margin vertical_padding position
let ypos = 297.0 - 5.0 - 50.0 - (140.0 * pos as f64);
add_qrcode_image_to_page(current_layer, scaledimg, finalsize, Mm(10.0), Mm(ypos));
add_address_at(current_layer, font, font_bold, title, address, &scaledimg, finalsize, ypos);
}
current_layer.use_text("ZEC Address (Sapling)", 14, Mm(55.0), Mm(ypos+27.5), &font_bold);
let strs = split_to_max(&address, 39, 6);
fn add_address_at(current_layer: &PdfLayerReference, font: &IndirectFontRef, font_bold: &IndirectFontRef, title: &str, address: &str, qrcode: &Vec<u8>, finalsize: usize, ypos: f64) {
add_qrcode_image_to_page(current_layer, qrcode, finalsize, Mm(10.0), Mm(ypos));
current_layer.use_text(title, 14, Mm(55.0), Mm(ypos+27.5), &font_bold);
let strs = split_to_max(&address, 39, 39); // No spaces, so user can copy the address
for i in 0..strs.len() {
current_layer.use_text(strs[i].clone(), 12, Mm(55.0), Mm(ypos+15.0-((i*5) as f64)), &font);
current_layer.use_text(strs[i].clone(), 12, Mm(55.0), Mm(ypos+20.0-((i*5) as f64)), &font);
}
}
/**
* Add the private key section to the PDF at `pos`, which can effectively be only 0 or 1.
*/
fn add_pk_to_page(current_layer: &PdfLayerReference, font: &IndirectFontRef, font_bold: &IndirectFontRef, pk: &str, seed: &str, path: &str, pos: u32) {
let (scaledimg, finalsize) = qrcode_scaled(pk, 10);
fn add_pk_to_page(current_layer: &PdfLayerReference, font: &IndirectFontRef, font_bold: &IndirectFontRef, pk: &str, address: &str, is_taddr: bool, seed: &str, path: &str, pos: u32) {
// page_height top_margin vertical_padding position
let ypos = 297.0 - 5.0 - 90.0 - (140.0 * pos as f64);
let line1 = Line {
points: vec![(Point::new(Mm(5.0), Mm(ypos + 50.0)), false), (Point::new(Mm(205.0), Mm(ypos + 50.0)), false)],
is_closed: true,
has_fill: false,
has_stroke: true,
is_clipping_path: false,
};
let outline_color = printpdf::Color::Rgb(Rgb::new(0.0, 0.0, 0.0, None));
current_layer.set_outline_color(outline_color);
let mut dash_pattern = LineDashPattern::default();
dash_pattern.dash_1 = Some(5);
current_layer.set_line_dash_pattern(dash_pattern);
current_layer.set_outline_thickness(1.0);
// Draw first line
current_layer.add_shape(line1);
// page_height top_margin vertical_padding position
let ypos = 297.0 - 5.0 - 100.0 - (140.0 * pos as f64);
add_qrcode_image_to_page(current_layer, scaledimg, finalsize, Mm(145.0), Mm(ypos-17.5));
// Reset the dashed line pattern
current_layer.set_line_dash_pattern(LineDashPattern::default());
current_layer.use_text("Private Key", 14, Mm(10.0), Mm(ypos+32.5), &font_bold);
let strs = split_to_max(&pk, 45, 10);
let (scaledimg, finalsize) = qrcode_scaled(pk, if is_taddr {20} else {10});
add_qrcode_image_to_page(current_layer, &scaledimg, finalsize, Mm(145.0), Mm(ypos-17.5));
current_layer.use_text("Private Key", 14, Mm(10.0), Mm(ypos+37.5), &font_bold);
let strs = split_to_max(&pk, 45, 45); // No spaces, so user can copy the private key
for i in 0..strs.len() {
current_layer.use_text(strs[i].clone(), 12, Mm(10.0), Mm(ypos+25.0-((i*5) as f64)), &font);
current_layer.use_text(strs[i].clone(), 12, Mm(10.0), Mm(ypos+32.5-((i*5) as f64)), &font);
}
// And add the seed too.
// Add the address a second time below the private key
let title = if is_taddr {"T Address"} else {"ZEC Address (Sapling)"};
current_layer.use_text(title, 12, Mm(10.0), Mm(ypos-10.0), &font_bold);
let strs = split_to_max(&address, 39, 39); // No spaces, so user can copy the address
for i in 0..strs.len() {
current_layer.use_text(strs[i].clone(), 12, Mm(10.0), Mm(ypos-15.0-((i*5) as f64)), &font);
}
current_layer.use_text(format!("HDSeed: {}, Path: {}", seed, path).as_str(), 8, Mm(10.0), Mm(ypos-25.0), &font);
// And add the seed too.
if !is_taddr {
current_layer.use_text(format!("HDSeed: {}, Path: {}", seed, path).as_str(), 8, Mm(10.0), Mm(ypos-35.0), &font);
}
}
/**
* Insert the given QRCode into the PDF at the given x,y co-ordinates. The qr code is a vector of RGB values.
*/
fn add_qrcode_image_to_page(current_layer: &PdfLayerReference, qr: Vec<u8>, qrsize: usize, x: Mm, y: Mm) {
fn add_qrcode_image_to_page(current_layer: &PdfLayerReference, qr: &Vec<u8>, qrsize: usize, x: Mm, y: Mm) {
// you can also construct images manually from your data:
let image_file_2 = ImageXObject {
width: Px(qrsize),
@ -161,7 +221,7 @@ fn add_qrcode_image_to_page(current_layer: &PdfLayerReference, qr: Vec<u8>, qrsi
/* put your bytes here. Make sure the total number of bytes =
width * height * (bytes per component * number of components)
(e.g. 2 (bytes) x 3 (colors) for RGB 16bit) */
image_data: qr,
image_data: qr.to_vec(),
image_filter: None, /* does not work yet */
clipping_bbox: None, /* doesn't work either, untested */
};
@ -207,6 +267,58 @@ fn split_to_max(s: &str, max: usize, blocksize: usize) -> Vec<String> {
#[cfg(test)]
mod tests {
#[test]
fn test_qrcode_scale() {
use array2d::Array2D;
use qrcode::QrCode;
use crate::pdf::qrcode_scaled;
let testdata = "This is some testdata";
let code = QrCode::new(testdata.as_bytes()).unwrap();
let width = code.width();
let factor = 10;
let padding = 10;
let (scaled, size) = qrcode_scaled(testdata, factor);
let scaled_size = (width * factor)+(2*padding);
assert_eq!(size, scaled_size);
// 3 bytes per pixel
let scaled_qrcode = Array2D::from_row_major(&scaled, scaled_size, scaled_size*3);
for i in 0..scaled_size {
for j in 0..scaled_size {
// The padding should be white
if i < padding || i >= (width*factor) + padding ||
j < padding || j >= (width*factor) + padding {
for px in 0..3 {
assert_eq!(scaled_qrcode[(i, j*3+px)], 255u8);
}
} else {
// Should match the QR code module
let module_i = (i-padding)/factor;
let module_j = (j-padding)/factor;
// This should really be (i,j), but I think there's a bug in the qrcode
// module that is returning it the other way.
let color = if code[(module_j, module_i)] == qrcode::Color::Light {
// Light color is white
255u8
} else {
// Dark color is black
0u8
};
for px in 0..3 {
assert_eq!(scaled_qrcode[(i, j*3+px)], color);
}
}
}
}
}
#[test]
fn test_split() {
@ -218,11 +330,13 @@ mod tests {
assert_eq!(split_to_max(addr, 44, 8).join("\n"), "ztestsap ling1w00 pdjthkzm zgut4c3y 7hu6q6c8 ferj\nczyvc03x wu0rvdgt re8a25em 5w3w6jxg hvcar5jz ehnn\n");
assert_eq!(split_to_max(addr, 44, 8).join(" ").replace(" ", ""), addr);
assert_eq!(split_to_max(addr, 42, 8).join(" ").replace(" ", ""), addr);
assert_eq!(split_to_max(addr, 39, 39).join(" ").replace(" ", ""), addr);
// Test the PK splitting using max/blocksize we'll know we use
let pk = "secret-extended-key-test1qj7vst8eqqqqqqpu2w6r0p2ykewm95h3d28k7r7y87e9p4v5zhzd4hj2y57clsprjveg997vqk7ak9tr2pnyyxmfzyzs6dhtuflt3aea9srp08teskpqfy2dtm07n08z3dyra407xumf3fk9ds4x06rzur7mgfyu39krj2g28lsxsxtv7swzu0j9vw4qf8rn5z72ztgeqj6u5zehylqm75c7d3um9ds9zvek4tdyta7qhln5fkc0dks6qwmkvr48fvgucpc3542kmdc97uqzt";
assert_eq!(split_to_max(pk, 44, 8).join(" ").replace(" ", ""), pk);
assert_eq!(split_to_max(pk, 45, 10).join(" ").replace(" ", ""), pk);
assert_eq!(split_to_max(pk, 45, 45).join(" ").replace(" ", ""), pk);
// Test random combinations of block size and spaces to ensure that
// the string is always preserved

16
qtlib/Makefile

@ -1,16 +0,0 @@
ifeq ($(shell uname),Darwin)
EXT := dylib
else
EXT := a
endif
all: target/debug/zecpaperrust.$(EXT)
g++ src/main.cpp -L./target/debug -lzecpaperrust -lpthread -ldl -o run
./run
target/debug/zecpaperrust.$(EXT): src/lib.rs Cargo.toml
cargo build --lib
clean:
rm -rf target
rm -rf run

61
qtlib/src/lib.rs

@ -1,61 +0,0 @@
use libc::{c_char};
use std::ffi::{CStr, CString};
use zecpaperlib::{paper, pdf};
#[no_mangle]
pub extern fn rust_generate_wallet(testnet: bool, count: u32) -> *mut c_char {
let c_str = CString::new(paper::generate_wallet(testnet, false, count)).unwrap();
return c_str.into_raw();
}
#[no_mangle]
pub extern fn rust_save_to_pdf(i_addrs: *const c_char, i_filename: *const c_char) {
let c_addrs = unsafe {
assert!(!i_addrs.is_null());
CStr::from_ptr(i_addrs)
};
let c_filename = unsafe {
assert!(!i_filename.is_null());
CStr::from_ptr(i_filename)
};
let addresses = c_addrs.to_str().unwrap();
let filename = c_filename.to_str().unwrap();
pdf::save_to_pdf(addresses, filename);
}
#[no_mangle]
pub extern fn rust_free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return }
CString::from_raw(s)
};
}
// #[no_mangle]
// pub extern fn double_input(input: i32) -> i32 {
// input * 2
// }
// #[no_mangle]
// pub extern fn say_hello() -> *mut c_char {
// let mut hello = String::from("Hello World");
// hello.push_str(", ZecWallet!");
// let c_str_song = CString::new(hello).unwrap();
// c_str_song.into_raw()
// }
// #[no_mangle]
// pub extern fn free_str(s: *mut c_char) {
// let s = unsafe {
// if s.is_null() { return }
// CString::from_raw(s)
// };
// println!("Freeing {:?}", s);
// }

15
qtlib/src/zecpaperrust.h

@ -1,15 +0,0 @@
#ifndef _ZEC_PAPER_RUST_H
#define _ZEC_PAPER_RUST_H
#ifdef __cplusplus
extern "C"{
#endif
extern char * rust_generate_wallet(bool testnet, unsigned int count);
extern void rust_free_string(char * s);
extern void rust_save_to_pdf(const char* addr, const char* filename);
#ifdef __cplusplus
}
#endif
#endif

6
ui/.gitignore

@ -0,0 +1,6 @@
bin/
Makefile
.qmake.stash
papersapling
papersapling.pro.user
.vscode/

86
ui/papersapling.pro

@ -0,0 +1,86 @@
#-------------------------------------------------
#
# Project created by QtCreator 2019-05-23T09:37:52
#
#-------------------------------------------------
QT += core gui printsupport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = papersapling
TEMPLATE = app
MOC_DIR = bin
OBJECTS_DIR = bin
UI_DIR = src
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
CONFIG += c++14
CONFIG += precompile_header
PRECOMPILED_HEADER = src/precompiled.h
SOURCES += \
src/main.cpp \
src/mainwindow.cpp \
src/qrcodelabel.cpp \
src/qrcode/BitBuffer.cpp \
src/qrcode/QrCode.cpp \
src/qrcode/QrSegment.cpp \
src/ellidedlabel.cpp
HEADERS += \
src/mainwindow.h \
src/qrcodelabel.h \
src/precompiled.h \
src/qrcode/BitBuffer.hpp \
src/qrcode/QrCode.hpp \
src/qrcode/QrSegment.hpp \
src/ellidedlabel.h \
qtlib/src/zecpaperrust.h
FORMS += \
src/mainwindow.ui \
src/wallet.ui
# Rust library
INCLUDEPATH += $$PWD/qtlib/src
DEPENDPATH += $$PWD/qtlib/src
librust.target = $$PWD/qtlib/target/release/libzecpaperrust.a
librust.commands = $(MAKE) -C $$PWD/qtlib
librustclean.commands = "rm -rf $$PWD/qtlib/target"
distclean.depends += librustclean
QMAKE_EXTRA_TARGETS += librust librustclean distclean
QMAKE_CLEAN += $$PWD/qtlib/target/release/libzecpaperrust.a
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
win32: LIBS += -L$$PWD/qtlib/target/release -lzecpaperrust
else:unix: LIBS += -L$$PWD/qtlib/target/release -lzecpaperrust -ldl
win32-g++: PRE_TARGETDEPS += $$PWD/qtlib/target/release/libzecpaperrust.a
else:win32:!win32-g++: PRE_TARGETDEPS += $$PWD/qtlib/target/release/libzecpaperrust.lib
else:unix::PRE_TARGETDEPS += $$PWD/qtlib/target/release/libzecpaperrust.a

0
qtlib/.gitignore → ui/qtlib/.gitignore

2
qtlib/Cargo.toml → ui/qtlib/Cargo.toml

@ -10,4 +10,4 @@ crate-type = ["staticlib"]
[dependencies]
libc = "0.2.58"
zecpaperlib = { path = "../lib" }
zecpaperlib = { path = "../../lib" }

19
ui/qtlib/Makefile

@ -0,0 +1,19 @@
ifeq ($(shell uname),Darwin)
EXT := dylib
else
EXT := a
endif
all: release
release: target/release/zecpaperrust.$(EXT)
debug: target/debug/zecpaperrust.$(EXT)
target/release/zecpaperrust.$(EXT): src/lib.rs Cargo.toml
cargo build --lib --release
target/debug/zecpaperrust.$(EXT): src/lib.rs Cargo.toml
cargo build --lib
clean:
rm -rf target

56
ui/qtlib/src/lib.rs

@ -0,0 +1,56 @@
use libc::{c_char};
use std::ffi::{CStr, CString};
use zecpaperlib::{pdf, paper};
/**
* Call into rust to generate a paper wallet. Returns the paper wallet in JSON form.
* NOTE: the returned string is owned by rust, so the caller needs to call rust_free_string with it
* after using it to free it properly
*/
#[no_mangle]
pub extern fn rust_generate_wallet(testnet: bool, zcount: u32, tcount: u32, entropy: *const c_char) -> *mut c_char {
let entropy_str = unsafe {
assert!(!entropy.is_null());
CStr::from_ptr(entropy)
};
let c_str = CString::new(paper::generate_wallet(testnet, false, zcount, tcount, entropy_str.to_bytes())).unwrap();
return c_str.into_raw();
}
#[no_mangle]
pub extern fn rust_save_as_pdf(json: *const c_char, file: *const c_char)-> bool {
let json_str = unsafe {
assert!(!json.is_null());
CStr::from_ptr(json)
};
let file_str = unsafe {
assert!(!file.is_null());
CStr::from_ptr(file)
};
match pdf::save_to_pdf(json_str.to_str().unwrap(), file_str.to_str().unwrap()) {
Ok(_) => return true,
Err(e) => {
eprintln!("{}", e);
return false;
}
}
}
/**
* Callers that receive string return values from other functions should call this to return the string
* back to rust, so it can be freed. Failure to call this function will result in a memory leak
*/
#[no_mangle]
pub extern fn rust_free_string(s: *mut c_char) {
unsafe {
if s.is_null() { return }
CString::from_raw(s)
};
}

2
qtlib/src/main.cpp → ui/qtlib/src/main.cpp

@ -6,7 +6,7 @@
using namespace std;
int main() {
char * from_rust = rust_generate_wallet(true, 1);
char * from_rust = rust_generate_wallet(true, 1, 1, "user-provided-entropy");
auto stri = string(from_rust);
rust_free_string(from_rust);

15
ui/qtlib/src/zecpaperrust.h

@ -0,0 +1,15 @@
#ifndef _ZEC_PAPER_RUST_H
#define _ZEC_PAPER_RUST_H
#ifdef __cplusplus
extern "C"{
#endif
extern char * rust_generate_wallet(bool testnet, unsigned int zcount, unsigned int tcount, const char* entropy);
extern void rust_free_string(char* s);
extern bool rust_save_as_pdf(const char* json, const char* filename);
#ifdef __cplusplus
}
#endif
#endif

59
ui/src/ellidedlabel.cpp

@ -0,0 +1,59 @@
#include "ellidedlabel.h"
EllidedLabel::EllidedLabel(QWidget *parent, const QString &text)
: QFrame(parent)
, elided(false)
, content(text)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
void EllidedLabel::setText(const QString &newText)
{
content = newText;
update();
}
void EllidedLabel::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
QPainter painter(this);
QFontMetrics fontMetrics = painter.fontMetrics();
bool didElide = false;
int lineSpacing = fontMetrics.lineSpacing();
int y = 0;
QTextLayout textLayout(content, painter.font());
textLayout.beginLayout();
forever {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
line.setLineWidth(width());
int nextLineY = y + lineSpacing;
if (height() >= nextLineY + lineSpacing) {
line.draw(&painter, QPoint(0, y));
y = nextLineY;
//! [2]
//! [3]
} else {
QString lastLine = content.mid(line.textStart());
QString elidedLastLine = fontMetrics.elidedText(lastLine, Qt::ElideRight, width());
painter.drawText(QPoint(0, y + fontMetrics.ascent()), elidedLastLine);
line = textLayout.createLine();
didElide = line.isValid();
break;
}
}
textLayout.endLayout();
if (didElide != elided) {
elided = didElide;
emit elisionChanged(didElide);
}
}

30
ui/src/ellidedlabel.h

@ -0,0 +1,30 @@
#ifndef ELLIDEDLABEL_H
#define ELLIDEDLABEL_H
#include "precompiled.h"
class EllidedLabel : public QFrame
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(bool isElided READ isElided)
public:
explicit EllidedLabel(QWidget *parent = nullptr, const QString &text = "");
void setText(const QString &text);
const QString & text() const { return content; }
bool isElided() const { return elided; }
protected:
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
signals:
void elisionChanged(bool elided);
private:
bool elided;
QString content;
};
#endif // ELLIDEDLABEL_H

11
ui/src/main.cpp

@ -0,0 +1,11 @@
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

134
ui/src/mainwindow.cpp

@ -0,0 +1,134 @@
#include "precompiled.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "ui_wallet.h"
#include "zecpaperrust.h"
QString SplitIntoLines(QString s, int maxlen) {
if (s.length() <= maxlen)
return s;
QStringList ans;
int start = 0;
for (int i=0; i < (s.length() / maxlen) + 1; i++) {
ans << s.mid(start, maxlen);
start += maxlen;
}
return ans.join("\n");
}
/**
* Add a wallet (address + pk) section to the given vertical layout
*/
void AddWallet(QString address, QString pk, QWidget* scroll) {
Ui_WalletWidget w;
auto g1 = new QGroupBox(scroll);
w.setupUi(g1);
scroll->layout()->addWidget(g1);
// Setup fixed with fonts
const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
w.qrAddress->setQrcodeString(address);
w.lblAddress->setText(SplitIntoLines(address, 39));
w.lblAddress->setFont(fixedFont);
w.qrPrivateKey->setQrcodeString(pk);
w.lblPrivateKey->setText(SplitIntoLines(pk, 59));
w.lblPrivateKey->setFont(fixedFont);
}
/**
* Generate wallets and return a JSON.
*/
QString Generate(int zaddrs, int taddrs, QString entropy) {
// Call into rust to get the addresses
char* wallet = rust_generate_wallet(false, zaddrs, taddrs, entropy.toStdString().c_str());
QString walletJson(wallet);
rust_free_string(wallet);
return walletJson;
}
void MainWindow::populateWallets() {
// First, get the numbers
int zaddrs = ui->txtzaddrs->text().toInt();
int taddrs = ui->txttaddrs->text().toInt();
QString entropy = ui->txtEntropy->text();
currentWallets = Generate(zaddrs, taddrs, entropy);
// Then, clear the Scroll area
auto children = ui->scroll->findChildren<QGroupBox *>();
for (int i=0; i < children.length(); i++) {
delete children[i];
}
// Then add the new wallets
auto json = QJsonDocument::fromJson(currentWallets.toUtf8());
for (int i=0; i < json.array().size(); i++) {
auto addr = json.array()[i].toObject()["address"].toString();
auto pk = json.array()[i].toObject()["private_key"].toString();
AddWallet(addr, pk, ui->scroll);
}
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// First, set up the validators for the number fields
intValidator = new QIntValidator(0, 25);
ui->txttaddrs->setValidator(intValidator);
ui->txtzaddrs->setValidator(intValidator);
// Wire up the generate button
QObject::connect(ui->btnGenerate, &QPushButton::clicked, [=]() {
this->populateWallets();
});
// Save as PDF
QObject::connect(ui->btnSavePDF, &QPushButton::clicked, [=]() {
// Get a save file name
auto filename = QFileDialog::getSaveFileName(this, tr("Save as PDF"), "", tr("PDF Files (*.pdf)"));
if (!filename.isEmpty()) {
bool success = rust_save_as_pdf(this->currentWallets.toStdString().c_str(), filename.toStdString().c_str());
if (success) {
QMessageBox::information(this, tr("Saved!"), tr("The wallets were saved to ") + filename);
} else {
QMessageBox::warning(this, tr("Failed to save file"),
tr("Couldn't save the file for some unknown reason"));
}
}
});
// Save as JSON
QObject::connect(ui->btnSaveJSON, &QPushButton::clicked, [=]() {
auto filename = QFileDialog::getSaveFileName(this, tr("Save as text"), "", tr("Text Files (*.txt)"));
if (!filename.isEmpty()) {
QFile file(filename);
if (file.open(QIODevice::WriteOnly))
{
QTextStream stream(&file);
stream << this->currentWallets << endl;
}
}
});
// Generate the default wallets
populateWallets();
}
MainWindow::~MainWindow()
{
delete ui;
delete intValidator;
}

29
ui/src/mainwindow.h

@ -0,0 +1,29 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "precompiled.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void populateWallets();
// The current JSON of the wallets.
QString currentWallets;
Ui::MainWindow *ui;
QIntValidator *intValidator;
};
#endif // MAINWINDOW_H

163
ui/src/mainwindow.ui

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>927</width>
<height>930</height>
</rect>
</property>
<property name="windowTitle">
<string>Zec Sapling Paper Wallet</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QGroupBox" name="Save">
<property name="title">
<string/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnSavePDF">
<property name="text">
<string>Save as PDF</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSaveJSON">
<property name="text">
<string>Save as JSON</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="Config">
<property name="title">
<string>Config</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Additional Entropy</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="txttaddrs">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QLineEdit" name="txtEntropy"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Number of z addresses</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txtzaddrs">
<property name="text">
<string>1</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="btnGenerate">
<property name="text">
<string>Generate Wallets</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Number of t addresses</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scroll">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>907</width>
<height>637</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>927</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>txtzaddrs</tabstop>
<tabstop>txttaddrs</tabstop>
<tabstop>txtEntropy</tabstop>
<tabstop>btnGenerate</tabstop>
<tabstop>btnSavePDF</tabstop>
<tabstop>btnSaveJSON</tabstop>
<tabstop>scrollArea</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

76
ui/src/precompiled.h

@ -0,0 +1,76 @@
#if defined __cplusplus
/* Add C++ includes here */
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <QtGlobal>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
#include <QRandomGenerator>
#endif
#include <QPrinter>
#include <QFontDatabase>
#include <QAbstractTableModel>
#include <QTranslator>
#include <QClipboard>
#include <QStringBuilder>
#include <QAbstractItemModel>
#include <QTableView>
#include <QHeaderView>
#include <QMessageBox>
#include <QCheckBox>
#include <QComboBox>
#include <QScrollBar>
#include <QPainter>
#include <QMovie>
#include <QPair>
#include <QVersionNumber>
#include <QDir>
#include <QMenu>
#include <QCompleter>
#include <QPushButton>
#include <QDateTime>
#include <QTimer>
#include <QSettings>
#include <QStyle>
#include <QFile>
#include <QTemporaryFile>
#include <QErrorMessage>
#include <QApplication>
#include <QWindow>
#include <QStandardPaths>
#include <QMainWindow>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QLabel>
#include <QDialog>
#include <QInputDialog>
#include <QFileDialog>
#include <QDebug>
#include <QUrl>
#include <QQueue>
#include <QProcess>
#include <QDesktopServices>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtWebSockets/QtWebSockets>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QAbstractTableModel>
#include <QAbstractItemModel>
#include <QObject>
#include <QApplication>
#include <QDesktopWidget>
#include "qrcode/QrCode.hpp"
#define QT6_VIRTUAL
#endif

41
ui/src/qrcode/BitBuffer.cpp

@ -0,0 +1,41 @@
/*
* QR Code generator library (C++)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#include <stdexcept>
#include "BitBuffer.hpp"
namespace qrcodegen {
BitBuffer::BitBuffer()
: std::vector<bool>() {}
void BitBuffer::appendBits(std::uint32_t val, int len) {
if (len < 0 || len > 31 || val >> len != 0)
throw std::domain_error("Value out of range");
for (int i = len - 1; i >= 0; i--) // Append bit by bit
this->push_back(((val >> i) & 1) != 0);
}
}

52
ui/src/qrcode/BitBuffer.hpp

@ -0,0 +1,52 @@
/*
* QR Code generator library (C++)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#pragma once
#include <cstdint>
#include <vector>
namespace qrcodegen {
/*
* An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.
*/
class BitBuffer final : public std::vector<bool> {
/*---- Constructor ----*/
// Creates an empty bit buffer (length 0).
public: BitBuffer();
/*---- Method ----*/
// Appends the given number of low-order bits of the given value
// to this buffer. Requires 0 <= len <= 31 and val < 2^len.
public: void appendBits(std::uint32_t val, int len);
};
}

620
ui/src/qrcode/QrCode.cpp

@ -0,0 +1,620 @@
/*
* QR Code generator library (C++)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#include <algorithm>
#include <climits>
#include <cstddef>
#include <cstdlib>
#include <sstream>
#include <stdexcept>
#include <utility>
#include "BitBuffer.hpp"
#include "QrCode.hpp"
using std::int8_t;
using std::uint8_t;
using std::size_t;
using std::vector;
namespace qrcodegen {
int QrCode::getFormatBits(Ecc ecl) {
switch (ecl) {
case Ecc::LOW : return 1;
case Ecc::MEDIUM : return 0;
case Ecc::QUARTILE: return 3;
case Ecc::HIGH : return 2;
default: throw std::logic_error("Assertion error");
}
}
QrCode QrCode::encodeText(const char *text, Ecc ecl) {
vector<QrSegment> segs = QrSegment::makeSegments(text);
return encodeSegments(segs, ecl);
}
QrCode QrCode::encodeBinary(const vector<uint8_t> &data, Ecc ecl) {
vector<QrSegment> segs{QrSegment::makeBytes(data)};
return encodeSegments(segs, ecl);
}
QrCode QrCode::encodeSegments(const vector<QrSegment> &segs, Ecc ecl,
int minVersion, int maxVersion, int mask, bool boostEcl) {
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)
throw std::invalid_argument("Invalid value");
// Find the minimal version number to use
int version, dataUsedBits;
for (version = minVersion; ; version++) {
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
dataUsedBits = QrSegment::getTotalBits(segs, version);
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
break; // This version number is found to be suitable
if (version >= maxVersion) // All versions in the range could not fit the given data
throw std::length_error("Data too long");
}
if (dataUsedBits == -1)
throw std::logic_error("Assertion error");
// Increase the error correction level while the data still fits in the current version number
for (Ecc newEcl : vector<Ecc>{Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high
if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8)
ecl = newEcl;
}
// Concatenate all segments to create the data bit string
BitBuffer bb;
for (const QrSegment &seg : segs) {
bb.appendBits(seg.getMode().getModeBits(), 4);
bb.appendBits(seg.getNumChars(), seg.getMode().numCharCountBits(version));
bb.insert(bb.end(), seg.getData().begin(), seg.getData().end());
}
if (bb.size() != static_cast<unsigned int>(dataUsedBits))
throw std::logic_error("Assertion error");
// Add terminator and pad up to a byte if applicable
size_t dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
if (bb.size() > dataCapacityBits)
throw std::logic_error("Assertion error");
bb.appendBits(0, std::min<size_t>(4, dataCapacityBits - bb.size()));
bb.appendBits(0, (8 - bb.size() % 8) % 8);
if (bb.size() % 8 != 0)
throw std::logic_error("Assertion error");
// Pad with alternating bytes until data capacity is reached
for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
bb.appendBits(padByte, 8);
// Pack bits into bytes in big endian
vector<uint8_t> dataCodewords(bb.size() / 8);
for (size_t i = 0; i < bb.size(); i++)
dataCodewords[i >> 3] |= (bb.at(i) ? 1 : 0) << (7 - (i & 7));
// Create the QR Code object
return QrCode(version, ecl, dataCodewords, mask);
}
QrCode::QrCode(int ver, Ecc ecl, const vector<uint8_t> &dataCodewords, int mask) :
// Initialize fields and check arguments
version(ver),
errorCorrectionLevel(ecl) {
if (ver < MIN_VERSION || ver > MAX_VERSION)
throw std::domain_error("Version value out of range");
if (mask < -1 || mask > 7)
throw std::domain_error("Mask value out of range");
size = ver * 4 + 17;
modules = vector<vector<bool> >(size, vector<bool>(size)); // Initially all white
isFunction = vector<vector<bool> >(size, vector<bool>(size));
// Compute ECC, draw modules, do masking
drawFunctionPatterns();
const vector<uint8_t> allCodewords = addEccAndInterleave(dataCodewords);
drawCodewords(allCodewords);
this->mask = handleConstructorMasking(mask);
isFunction.clear();
isFunction.shrink_to_fit();
}
int QrCode::getVersion() const {
return version;
}
int QrCode::getSize() const {
return size;
}
QrCode::Ecc QrCode::getErrorCorrectionLevel() const {
return errorCorrectionLevel;
}
int QrCode::getMask() const {
return mask;
}
bool QrCode::getModule(int x, int y) const {
return 0 <= x && x < size && 0 <= y && y < size && module(x, y);
}
std::string QrCode::toSvgString(int border) const {
if (border < 0)
throw std::domain_error("Border must be non-negative");
if (border > INT_MAX / 2 || border * 2 > INT_MAX - size)
throw std::overflow_error("Border too large");
std::ostringstream sb;
sb << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
sb << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
sb << "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 ";
sb << (size + border * 2) << " " << (size + border * 2) << "\" stroke=\"none\">\n";
sb << "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n";
sb << "\t<path d=\"";
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
if (getModule(x, y)) {
if (x != 0 || y != 0)
sb << " ";
sb << "M" << (x + border) << "," << (y + border) << "h1v1h-1z";
}
}
}
sb << "\" fill=\"#000000\"/>\n";
sb << "</svg>\n";
return sb.str();
}
void QrCode::drawFunctionPatterns() {
// Draw horizontal and vertical timing patterns
for (int i = 0; i < size; i++) {
setFunctionModule(6, i, i % 2 == 0);
setFunctionModule(i, 6, i % 2 == 0);
}
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
drawFinderPattern(3, 3);
drawFinderPattern(size - 4, 3);
drawFinderPattern(3, size - 4);
// Draw numerous alignment patterns
const vector<int> alignPatPos = getAlignmentPatternPositions();
int numAlign = alignPatPos.size();
for (int i = 0; i < numAlign; i++) {
for (int j = 0; j < numAlign; j++) {
// Don't draw on the three finder corners
if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)))
drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j));
}
}
// Draw configuration data
drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
drawVersion();
}
void QrCode::drawFormatBits(int mask) {
// Calculate error correction code and pack bits
int data = getFormatBits(errorCorrectionLevel) << 3 | mask; // errCorrLvl is uint2, mask is uint3
int rem = data;
for (int i = 0; i < 10; i++)
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
int bits = (data << 10 | rem) ^ 0x5412; // uint15
if (bits >> 15 != 0)
throw std::logic_error("Assertion error");
// Draw first copy
for (int i = 0; i <= 5; i++)
setFunctionModule(8, i, getBit(bits, i));
setFunctionModule(8, 7, getBit(bits, 6));
setFunctionModule(8, 8, getBit(bits, 7));
setFunctionModule(7, 8, getBit(bits, 8));
for (int i = 9; i < 15; i++)
setFunctionModule(14 - i, 8, getBit(bits, i));
// Draw second copy
for (int i = 0; i <= 7; i++)
setFunctionModule(size - 1 - i, 8, getBit(bits, i));
for (int i = 8; i < 15; i++)
setFunctionModule(8, size - 15 + i, getBit(bits, i));
setFunctionModule(8, size - 8, true); // Always black
}
void QrCode::drawVersion() {
if (version < 7)
return;
// Calculate error correction code and pack bits
int rem = version; // version is uint6, in the range [7, 40]
for (int i = 0; i < 12; i++)
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
long bits = (long)version << 12 | rem; // uint18
if (bits >> 18 != 0)
throw std::logic_error("Assertion error");
// Draw two copies
for (int i = 0; i < 18; i++) {
bool bit = getBit(bits, i);
int a = size - 11 + i % 3;
int b = i / 3;
setFunctionModule(a, b, bit);
setFunctionModule(b, a, bit);
}
}
void QrCode::drawFinderPattern(int x, int y) {
for (int dy = -4; dy <= 4; dy++) {
for (int dx = -4; dx <= 4; dx++) {
int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm
int xx = x + dx, yy = y + dy;
if (0 <= xx && xx < size && 0 <= yy && yy < size)
setFunctionModule(xx, yy, dist != 2 && dist != 4);
}
}
}
void QrCode::drawAlignmentPattern(int x, int y) {
for (int dy = -2; dy <= 2; dy++) {
for (int dx = -2; dx <= 2; dx++)
setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1);
}
}
void QrCode::setFunctionModule(int x, int y, bool isBlack) {
modules.at(y).at(x) = isBlack;
isFunction.at(y).at(x) = true;
}
bool QrCode::module(int x, int y) const {
return modules.at(y).at(x);
}
vector<uint8_t> QrCode::addEccAndInterleave(const vector<uint8_t> &data) const {
if (data.size() != static_cast<unsigned int>(getNumDataCodewords(version, errorCorrectionLevel)))
throw std::invalid_argument("Invalid argument");
// Calculate parameter numbers
int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(errorCorrectionLevel)][version];
int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast<int>(errorCorrectionLevel)][version];
int rawCodewords = getNumRawDataModules(version) / 8;
int numShortBlocks = numBlocks - rawCodewords % numBlocks;
int shortBlockLen = rawCodewords / numBlocks;
// Split data into blocks and append ECC to each block
vector<vector<uint8_t> > blocks;
const ReedSolomonGenerator rs(blockEccLen);
for (int i = 0, k = 0; i < numBlocks; i++) {
vector<uint8_t> dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)));
k += dat.size();
const vector<uint8_t> ecc = rs.getRemainder(dat);
if (i < numShortBlocks)
dat.push_back(0);
dat.insert(dat.end(), ecc.cbegin(), ecc.cend());
blocks.push_back(std::move(dat));
}
// Interleave (not concatenate) the bytes from every block into a single sequence
vector<uint8_t> result;
for (size_t i = 0; i < blocks.at(0).size(); i++) {
for (size_t j = 0; j < blocks.size(); j++) {
// Skip the padding byte in short blocks
if (i != static_cast<unsigned int>(shortBlockLen - blockEccLen) || j >= static_cast<unsigned int>(numShortBlocks))
result.push_back(blocks.at(j).at(i));
}
}
if (result.size() != static_cast<unsigned int>(rawCodewords))
throw std::logic_error("Assertion error");
return result;
}
void QrCode::drawCodewords(const vector<uint8_t> &data) {
if (data.size() != static_cast<unsigned int>(getNumRawDataModules(version) / 8))
throw std::invalid_argument("Invalid argument");
size_t i = 0; // Bit index into the data
// Do the funny zigzag scan
for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
if (right == 6)
right = 5;
for (int vert = 0; vert < size; vert++) { // Vertical counter
for (int j = 0; j < 2; j++) {
int x = right - j; // Actual x coordinate
bool upward = ((right + 1) & 2) == 0;
int y = upward ? size - 1 - vert : vert; // Actual y coordinate
if (!isFunction.at(y).at(x) && i < data.size() * 8) {
modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast<int>(i & 7));
i++;
}
// If this QR Code has any remainder bits (0 to 7), they were assigned as
// 0/false/white by the constructor and are left unchanged by this method
}
}
}
if (i != data.size() * 8)
throw std::logic_error("Assertion error");
}
void QrCode::applyMask(int mask) {
if (mask < 0 || mask > 7)
throw std::domain_error("Mask value out of range");
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
bool invert;
switch (mask) {
case 0: invert = (x + y) % 2 == 0; break;
case 1: invert = y % 2 == 0; break;
case 2: invert = x % 3 == 0; break;
case 3: invert = (x + y) % 3 == 0; break;
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
default: throw std::logic_error("Assertion error");
}
modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x));
}
}
}
int QrCode::handleConstructorMasking(int mask) {
if (mask == -1) { // Automatically choose best mask
long minPenalty = LONG_MAX;
for (int i = 0; i < 8; i++) {
drawFormatBits(i);
applyMask(i);
long penalty = getPenaltyScore();
if (penalty < minPenalty) {
mask = i;
minPenalty = penalty;
}
applyMask(i); // Undoes the mask due to XOR
}
}
if (mask < 0 || mask > 7)
throw std::logic_error("Assertion error");
drawFormatBits(mask); // Overwrite old format bits
applyMask(mask); // Apply the final choice of mask
return mask; // The caller shall assign this value to the final-declared field
}
long QrCode::getPenaltyScore() const {
long result = 0;
// Adjacent modules in row having same color
for (int y = 0; y < size; y++) {
bool colorX = false;
for (int x = 0, runX = -1; x < size; x++) {
if (x == 0 || module(x, y) != colorX) {
colorX = module(x, y);
runX = 1;
} else {
runX++;
if (runX == 5)
result += PENALTY_N1;
else if (runX > 5)
result++;
}
}
}
// Adjacent modules in column having same color
for (int x = 0; x < size; x++) {
bool colorY = false;
for (int y = 0, runY = -1; y < size; y++) {
if (y == 0 || module(x, y) != colorY) {
colorY = module(x, y);
runY = 1;
} else {
runY++;
if (runY == 5)
result += PENALTY_N1;
else if (runY > 5)
result++;
}
}
}
// 2*2 blocks of modules having same color
for (int y = 0; y < size - 1; y++) {
for (int x = 0; x < size - 1; x++) {
bool color = module(x, y);
if ( color == module(x + 1, y) &&
color == module(x, y + 1) &&
color == module(x + 1, y + 1))
result += PENALTY_N2;
}
}
// Finder-like pattern in rows
for (int y = 0; y < size; y++) {
for (int x = 0, bits = 0; x < size; x++) {
bits = ((bits << 1) & 0x7FF) | (module(x, y) ? 1 : 0);
if (x >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated
result += PENALTY_N3;
}
}
// Finder-like pattern in columns
for (int x = 0; x < size; x++) {
for (int y = 0, bits = 0; y < size; y++) {
bits = ((bits << 1) & 0x7FF) | (module(x, y) ? 1 : 0);
if (y >= 10 && (bits == 0x05D || bits == 0x5D0)) // Needs 11 bits accumulated
result += PENALTY_N3;
}
}
// Balance of black and white modules
int black = 0;
for (const vector<bool> &row : modules) {
for (bool color : row) {
if (color)
black++;
}
}
int total = size * size; // Note that size is odd, so black/total != 1/2
// Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
int k = static_cast<int>((std::abs(black * 20L - total * 10L) + total - 1) / total) - 1;
result += k * PENALTY_N4;
return result;
}
vector<int> QrCode::getAlignmentPatternPositions() const {
if (version == 1)
return vector<int>();
else {
int numAlign = version / 7 + 2;
int step = (version == 32) ? 26 :
(version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2;
vector<int> result;
for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step)
result.insert(result.begin(), pos);
result.insert(result.begin(), 6);
return result;
}
}
int QrCode::getNumRawDataModules(int ver) {
if (ver < MIN_VERSION || ver > MAX_VERSION)
throw std::domain_error("Version number out of range");
int result = (16 * ver + 128) * ver + 64;
if (ver >= 2) {
int numAlign = ver / 7 + 2;
result -= (25 * numAlign - 10) * numAlign - 55;
if (ver >= 7)
result -= 36;
}
return result;
}
int QrCode::getNumDataCodewords(int ver, Ecc ecl) {
return getNumRawDataModules(ver) / 8
- ECC_CODEWORDS_PER_BLOCK [static_cast<int>(ecl)][ver]
* NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(ecl)][ver];
}
bool QrCode::getBit(long x, int i) {
return ((x >> i) & 1) != 0;
}
/*---- Tables of constants ----*/
const int QrCode::PENALTY_N1 = 3;
const int QrCode::PENALTY_N2 = 3;
const int QrCode::PENALTY_N3 = 40;
const int QrCode::PENALTY_N4 = 10;
const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = {
// Version: (note that index 0 is for padding, and is set to an illegal value)
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
{-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
};
const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = {
// Version: (note that index 0 is for padding, and is set to an illegal value)
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
};
QrCode::ReedSolomonGenerator::ReedSolomonGenerator(int degree) :
coefficients() {
if (degree < 1 || degree > 255)
throw std::domain_error("Degree out of range");
// Start with the monomial x^0
coefficients.resize(degree);
coefficients.at(degree - 1) = 1;
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
// drop the highest term, and store the rest of the coefficients in order of descending powers.
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
uint8_t root = 1;
for (int i = 0; i < degree; i++) {
// Multiply the current product by (x - r^i)
for (size_t j = 0; j < coefficients.size(); j++) {
coefficients.at(j) = multiply(coefficients.at(j), root);
if (j + 1 < coefficients.size())
coefficients.at(j) ^= coefficients.at(j + 1);
}
root = multiply(root, 0x02);
}
}
vector<uint8_t> QrCode::ReedSolomonGenerator::getRemainder(const vector<uint8_t> &data) const {
// Compute the remainder by performing polynomial division
vector<uint8_t> result(coefficients.size());
for (uint8_t b : data) {
uint8_t factor = b ^ result.at(0);
result.erase(result.begin());
result.push_back(0);
for (size_t j = 0; j < result.size(); j++)
result.at(j) ^= multiply(coefficients.at(j), factor);
}
return result;
}
uint8_t QrCode::ReedSolomonGenerator::multiply(uint8_t x, uint8_t y) {
// Russian peasant multiplication
int z = 0;
for (int i = 7; i >= 0; i--) {
z = (z << 1) ^ ((z >> 7) * 0x11D);
z ^= ((y >> i) & 1) * x;
}
if (z >> 8 != 0)
throw std::logic_error("Assertion error");
return static_cast<uint8_t>(z);
}
}

351
ui/src/qrcode/QrCode.hpp

@ -0,0 +1,351 @@
/*
* QR Code generator library (C++)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "QrSegment.hpp"
namespace qrcodegen {
/*
* A QR Code symbol, which is a type of two-dimension barcode.
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
* Instances of this class represent an immutable square grid of black and white cells.
* The class provides static factory functions to create a QR Code from text or binary data.
* The class covers the QR Code Model 2 specification, supporting all versions (sizes)
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
*
* Ways to create a QR Code object:
* - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary().
* - Mid level: Custom-make the list of segments and call QrCode::encodeSegments().
* - Low level: Custom-make the array of data codeword bytes (including
* segment headers and final padding, excluding error correction codewords),
* supply the appropriate version number, and call the QrCode() constructor.
* (Note that all ways require supplying the desired error correction level.)
*/
class QrCode final {
/*---- Public helper enumeration ----*/
/*
* The error correction level in a QR Code symbol.
*/
public: enum class Ecc {
LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords
MEDIUM , // The QR Code can tolerate about 15% erroneous codewords
QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
HIGH , // The QR Code can tolerate about 30% erroneous codewords
};
// Returns a value in the range 0 to 3 (unsigned 2-bit integer).
private: static int getFormatBits(Ecc ecl);
/*---- Static factory functions (high level) ----*/
/*
* Returns a QR Code representing the given Unicode text string at the given error correction level.
* As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer
* UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible
* QR Code version is automatically chosen for the output. The ECC level of the result may be higher than
* the ecl argument if it can be done without increasing the version.
*/
public: static QrCode encodeText(const char *text, Ecc ecl);
/*
* Returns a QR Code representing the given binary data at the given error correction level.
* This function always encodes using the binary segment mode, not any text mode. The maximum number of
* bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
* The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
*/
public: static QrCode encodeBinary(const std::vector<std::uint8_t> &data, Ecc ecl);
/*---- Static factory functions (mid level) ----*/
/*
* Returns a QR Code representing the given segments with the given encoding parameters.
* The smallest possible QR Code version within the given range is automatically
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
* may be higher than the ecl argument if it can be done without increasing the
* version. The mask number is either between 0 to 7 (inclusive) to force that
* mask, or -1 to automatically choose an appropriate mask (which may be slow).
* This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and byte) to encode text in less space.
* This is a mid-level API; the high-level API is encodeText() and encodeBinary().
*/
public: static QrCode encodeSegments(const std::vector<QrSegment> &segs, Ecc ecl,
int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters
/*---- Instance fields ----*/
// Immutable scalar parameters:
/* The version number of this QR Code, which is between 1 and 40 (inclusive).
* This determines the size of this barcode. */
private: int version;
/* The width and height of this QR Code, measured in modules, between
* 21 and 177 (inclusive). This is equal to version * 4 + 17. */
private: int size;
/* The error correction level used in this QR Code. */
private: Ecc errorCorrectionLevel;
/* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
* Even if a QR Code is created with automatic masking requested (mask = -1),
* the resulting object still has a mask value between 0 and 7. */
private: int mask;
// Private grids of modules/pixels, with dimensions of size*size:
// The modules of this QR Code (false = white, true = black).
// Immutable after constructor finishes. Accessed through getModule().
private: std::vector<std::vector<bool> > modules;
// Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
private: std::vector<std::vector<bool> > isFunction;
/*---- Constructor (low level) ----*/
/*
* Creates a new QR Code with the given version number,
* error correction level, data codeword bytes, and mask number.
* This is a low-level API that most users should not use directly.
* A mid-level API is the encodeSegments() function.
*/
public: QrCode(int ver, Ecc ecl, const std::vector<std::uint8_t> &dataCodewords, int mask);
/*---- Public instance methods ----*/
/*
* Returns this QR Code's version, in the range [1, 40].
*/
public: int getVersion() const;
/*
* Returns this QR Code's size, in the range [21, 177].
*/
public: int getSize() const;
/*
* Returns this QR Code's error correction level.
*/
public: Ecc getErrorCorrectionLevel() const;
/*
* Returns this QR Code's mask, in the range [0, 7].
*/
public: int getMask() const;
/*
* Returns the color of the module (pixel) at the given coordinates, which is false
* for white or true for black. The top left corner has the coordinates (x=0, y=0).
* If the given coordinates are out of bounds, then false (white) is returned.
*/
public: bool getModule(int x, int y) const;
/*
* Returns a string of SVG code for an image depicting this QR Code, with the given number
* of border modules. The string always uses Unix newlines (\n), regardless of the platform.
*/
public: std::string toSvgString(int border) const;
/*---- Private helper methods for constructor: Drawing function modules ----*/
// Reads this object's version field, and draws and marks all function modules.
private: void drawFunctionPatterns();
// Draws two copies of the format bits (with its own error correction code)
// based on the given mask and this object's error correction level field.
private: void drawFormatBits(int mask);
// Draws two copies of the version bits (with its own error correction code),
// based on this object's version field, iff 7 <= version <= 40.
private: void drawVersion();
// Draws a 9*9 finder pattern including the border separator,
// with the center module at (x, y). Modules can be out of bounds.
private: void drawFinderPattern(int x, int y);
// Draws a 5*5 alignment pattern, with the center module
// at (x, y). All modules must be in bounds.
private: void drawAlignmentPattern(int x, int y);
// Sets the color of a module and marks it as a function module.
// Only used by the constructor. Coordinates must be in bounds.
private: void setFunctionModule(int x, int y, bool isBlack);
// Returns the color of the module at the given coordinates, which must be in range.
private: bool module(int x, int y) const;
/*---- Private helper methods for constructor: Codewords and masking ----*/
// Returns a new byte string representing the given data with the appropriate error correction
// codewords appended to it, based on this object's version and error correction level.
private: std::vector<std::uint8_t> addEccAndInterleave(const std::vector<std::uint8_t> &data) const;
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
// data area of this QR Code. Function modules need to be marked off before this is called.
private: void drawCodewords(const std::vector<std::uint8_t> &data);
// XORs the codeword modules in this QR Code with the given mask pattern.
// The function modules must be marked and the codeword bits must be drawn
// before masking. Due to the arithmetic of XOR, calling applyMask() with
// the same mask value a second time will undo the mask. A final well-formed
// QR Code needs exactly one (not zero, two, etc.) mask applied.
private: void applyMask(int mask);
// A messy helper function for the constructors. This QR Code must be in an unmasked state when this
// method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed.
// This method applies and returns the actual mask chosen, from 0 to 7.
private: int handleConstructorMasking(int mask);
// Calculates and returns the penalty score based on state of this QR Code's current modules.
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
private: long getPenaltyScore() const;
/*---- Private helper functions ----*/
// Returns an ascending list of positions of alignment patterns for this version number.
// Each position is in the range [0,177), and are used on both the x and y axes.
// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.
private: std::vector<int> getAlignmentPatternPositions() const;
// Returns the number of data bits that can be stored in a QR Code of the given version number, after
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
private: static int getNumRawDataModules(int ver);
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
// QR Code of the given version number and error correction level, with remainder bits discarded.
// This stateless pure function could be implemented as a (40*4)-cell lookup table.
private: static int getNumDataCodewords(int ver, Ecc ecl);
// Returns true iff the i'th bit of x is set to 1.
private: static bool getBit(long x, int i);
/*---- Constants and tables ----*/
// The minimum version number supported in the QR Code Model 2 standard.
public: static constexpr int MIN_VERSION = 1;
// The maximum version number supported in the QR Code Model 2 standard.
public: static constexpr int MAX_VERSION = 40;
// For use in getPenaltyScore(), when evaluating which mask is best.
private: static const int PENALTY_N1;
private: static const int PENALTY_N2;
private: static const int PENALTY_N3;
private: static const int PENALTY_N4;
private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41];
private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41];
/*---- Private helper class ----*/
/*
* Computes the Reed-Solomon error correction codewords for a sequence of data codewords
* at a given degree. Objects are immutable, and the state only depends on the degree.
* This class exists because each data block in a QR Code shares the same the divisor polynomial.
*/
private: class ReedSolomonGenerator final {
/*-- Immutable field --*/
// Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which
// is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
private: std::vector<std::uint8_t> coefficients;
/*-- Constructor --*/
/*
* Creates a Reed-Solomon ECC generator for the given degree. This could be implemented
* as a lookup table over all possible parameter values, instead of as an algorithm.
*/
public: explicit ReedSolomonGenerator(int degree);
/*-- Method --*/
/*
* Computes and returns the Reed-Solomon error correction codewords for the given
* sequence of data codewords. The returned object is always a new byte array.
* This method does not alter this object's state (because it is immutable).
*/
public: std::vector<std::uint8_t> getRemainder(const std::vector<std::uint8_t> &data) const;
/*-- Static function --*/
// Returns the product of the two given field elements modulo GF(2^8/0x11D).
// All inputs are valid. This could be implemented as a 256*256 lookup table.
private: static std::uint8_t multiply(std::uint8_t x, std::uint8_t y);
};
};
}

225
ui/src/qrcode/QrSegment.cpp

@ -0,0 +1,225 @@
/*
* QR Code generator library (C++)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#include <climits>
#include <cstring>
#include <stdexcept>
#include <utility>
#include "QrSegment.hpp"
using std::uint8_t;
using std::vector;
namespace qrcodegen {
QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) :
modeBits(mode) {
numBitsCharCount[0] = cc0;
numBitsCharCount[1] = cc1;
numBitsCharCount[2] = cc2;
}
int QrSegment::Mode::getModeBits() const {
return modeBits;
}
int QrSegment::Mode::numCharCountBits(int ver) const {
return numBitsCharCount[(ver + 7) / 17];
}
const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14);
const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13);
const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16);
const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12);
const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0);
QrSegment QrSegment::makeBytes(const vector<uint8_t> &data) {
if (data.size() > static_cast<unsigned int>(INT_MAX))
throw std::length_error("Data too long");
BitBuffer bb;
for (uint8_t b : data)
bb.appendBits(b, 8);
return QrSegment(Mode::BYTE, static_cast<int>(data.size()), std::move(bb));
}
QrSegment QrSegment::makeNumeric(const char *digits) {
BitBuffer bb;
int accumData = 0;
int accumCount = 0;
int charCount = 0;
for (; *digits != '\0'; digits++, charCount++) {
char c = *digits;
if (c < '0' || c > '9')
throw std::domain_error("String contains non-numeric characters");
accumData = accumData * 10 + (c - '0');
accumCount++;
if (accumCount == 3) {
bb.appendBits(accumData, 10);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) // 1 or 2 digits remaining
bb.appendBits(accumData, accumCount * 3 + 1);
return QrSegment(Mode::NUMERIC, charCount, std::move(bb));
}
QrSegment QrSegment::makeAlphanumeric(const char *text) {
BitBuffer bb;
int accumData = 0;
int accumCount = 0;
int charCount = 0;
for (; *text != '\0'; text++, charCount++) {
const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text);
if (temp == nullptr)
throw std::domain_error("String contains unencodable characters in alphanumeric mode");
accumData = accumData * 45 + (temp - ALPHANUMERIC_CHARSET);
accumCount++;
if (accumCount == 2) {
bb.appendBits(accumData, 11);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) // 1 character remaining
bb.appendBits(accumData, 6);
return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb));
}
vector<QrSegment> QrSegment::makeSegments(const char *text) {
// Select the most efficient segment encoding automatically
vector<QrSegment> result;
if (*text == '\0'); // Leave result empty
else if (isNumeric(text))
result.push_back(makeNumeric(text));
else if (isAlphanumeric(text))
result.push_back(makeAlphanumeric(text));
else {
vector<uint8_t> bytes;
for (; *text != '\0'; text++)
bytes.push_back(static_cast<uint8_t>(*text));
result.push_back(makeBytes(bytes));
}
return result;
}
QrSegment QrSegment::makeEci(long assignVal) {
BitBuffer bb;
if (assignVal < 0)
throw std::domain_error("ECI assignment value out of range");
else if (assignVal < (1 << 7))
bb.appendBits(assignVal, 8);
else if (assignVal < (1 << 14)) {
bb.appendBits(2, 2);
bb.appendBits(assignVal, 14);
} else if (assignVal < 1000000L) {
bb.appendBits(6, 3);
bb.appendBits(assignVal, 21);
} else
throw std::domain_error("ECI assignment value out of range");
return QrSegment(Mode::ECI, 0, std::move(bb));
}
QrSegment::QrSegment(Mode md, int numCh, const std::vector<bool> &dt) :
mode(md),
numChars(numCh),
data(dt) {
if (numCh < 0)
throw std::domain_error("Invalid value");
}
QrSegment::QrSegment(Mode md, int numCh, std::vector<bool> &&dt) :
mode(md),
numChars(numCh),
data(std::move(dt)) {
if (numCh < 0)
throw std::domain_error("Invalid value");
}
int QrSegment::getTotalBits(const vector<QrSegment> &segs, int version) {
int result = 0;
for (const QrSegment &seg : segs) {
int ccbits = seg.mode.numCharCountBits(version);
if (seg.numChars >= (1L << ccbits))
return -1; // The segment's length doesn't fit the field's bit width
if (4 + ccbits > INT_MAX - result)
return -1; // The sum will overflow an int type
result += 4 + ccbits;
if (seg.data.size() > static_cast<unsigned int>(INT_MAX - result))
return -1; // The sum will overflow an int type
result += static_cast<int>(seg.data.size());
}
return result;
}
bool QrSegment::isAlphanumeric(const char *text) {
for (; *text != '\0'; text++) {
if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr)
return false;
}
return true;
}
bool QrSegment::isNumeric(const char *text) {
for (; *text != '\0'; text++) {
char c = *text;
if (c < '0' || c > '9')
return false;
}
return true;
}
QrSegment::Mode QrSegment::getMode() const {
return mode;
}
int QrSegment::getNumChars() const {
return numChars;
}
const std::vector<bool> &QrSegment::getData() const {
return data;
}
const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
}

216
ui/src/qrcode/QrSegment.hpp

@ -0,0 +1,216 @@
/*
* QR Code generator library (C++)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
#pragma once
#include <cstdint>
#include <vector>
#include "BitBuffer.hpp"
namespace qrcodegen {
/*
* A segment of character/binary/control data in a QR Code symbol.
* Instances of this class are immutable.
* The mid-level way to create a segment is to take the payload data
* and call a static factory function such as QrSegment::makeNumeric().
* The low-level way to create a segment is to custom-make the bit buffer
* and call the QrSegment() constructor with appropriate values.
* This segment class imposes no length restrictions, but QR Codes have restrictions.
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
*/
class QrSegment final {
/*---- Public helper enumeration ----*/
/*
* Describes how a segment's data bits are interpreted. Immutable.
*/
public: class Mode final {
/*-- Constants --*/
public: static const Mode NUMERIC;
public: static const Mode ALPHANUMERIC;
public: static const Mode BYTE;
public: static const Mode KANJI;
public: static const Mode ECI;
/*-- Fields --*/
// The mode indicator bits, which is a uint4 value (range 0 to 15).
private: int modeBits;
// Number of character count bits for three different version ranges.
private: int numBitsCharCount[3];
/*-- Constructor --*/
private: Mode(int mode, int cc0, int cc1, int cc2);
/*-- Methods --*/
/*
* (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15).
*/
public: int getModeBits() const;
/*
* (Package-private) Returns the bit width of the character count field for a segment in
* this mode in a QR Code at the given version number. The result is in the range [0, 16].
*/
public: int numCharCountBits(int ver) const;
};
/*---- Static factory functions (mid level) ----*/
/*
* Returns a segment representing the given binary data encoded in
* byte mode. All input byte vectors are acceptable. Any text string
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
*/
public: static QrSegment makeBytes(const std::vector<std::uint8_t> &data);
/*
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
*/
public: static QrSegment makeNumeric(const char *digits);
/*
* Returns a segment representing the given text string encoded in alphanumeric mode.
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
*/
public: static QrSegment makeAlphanumeric(const char *text);
/*
* Returns a list of zero or more segments to represent the given text string. The result
* may use various segment modes and switch modes to optimize the length of the bit stream.
*/
public: static std::vector<QrSegment> makeSegments(const char *text);
/*
* Returns a segment representing an Extended Channel Interpretation
* (ECI) designator with the given assignment value.
*/
public: static QrSegment makeEci(long assignVal);
/*---- Public static helper functions ----*/
/*
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
*/
public: static bool isAlphanumeric(const char *text);
/*
* Tests whether the given string can be encoded as a segment in numeric mode.
* A string is encodable iff each character is in the range 0 to 9.
*/
public: static bool isNumeric(const char *text);
/*---- Instance fields ----*/
/* The mode indicator of this segment. Accessed through getMode(). */
private: Mode mode;
/* The length of this segment's unencoded data. Measured in characters for
* numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
* Always zero or positive. Not the same as the data's bit length.
* Accessed through getNumChars(). */
private: int numChars;
/* The data bits of this segment. Accessed through getData(). */
private: std::vector<bool> data;
/*---- Constructors (low level) ----*/
/*
* Creates a new QR Code segment with the given attributes and data.
* The character count (numCh) must agree with the mode and the bit buffer length,
* but the constraint isn't checked. The given bit buffer is copied and stored.
*/
public: QrSegment(Mode md, int numCh, const std::vector<bool> &dt);
/*
* Creates a new QR Code segment with the given parameters and data.
* The character count (numCh) must agree with the mode and the bit buffer length,
* but the constraint isn't checked. The given bit buffer is moved and stored.
*/
public: QrSegment(Mode md, int numCh, std::vector<bool> &&dt);
/*---- Methods ----*/
/*
* Returns the mode field of this segment.
*/
public: Mode getMode() const;
/*
* Returns the character count field of this segment.
*/
public: int getNumChars() const;
/*
* Returns the data bits of this segment.
*/
public: const std::vector<bool> &getData() const;
// (Package-private) Calculates the number of bits needed to encode the given segments at
// the given version. Returns a non-negative number if successful. Otherwise returns -1 if a
// segment has too many characters to fit its length field, or the total bits exceeds INT_MAX.
public: static int getTotalBits(const std::vector<QrSegment> &segs, int version);
/*---- Private constant ----*/
/* The set of all legal characters in alphanumeric mode, where
* each character value maps to the index in the string. */
private: static const char *ALPHANUMERIC_CHARSET;
};
}

66
ui/src/qrcodelabel.cpp

@ -0,0 +1,66 @@
#include "qrcodelabel.h"
QRCodeLabel::QRCodeLabel(QWidget *parent) :
QLabel(parent)
{
this->setMinimumSize(100, 100);
setScaledContents(false);
}
QSize QRCodeLabel::sizeHint() const
{
int w = this->width();
return QSize(w, w); // 1:1
}
void QRCodeLabel::resizeEvent(QResizeEvent*)
{
if(!str.isEmpty())
QLabel::setPixmap(scaledPixmap());
}
QPixmap QRCodeLabel::scaledPixmap() const {
QPixmap pm(size());
pm.fill(Qt::white);
QPainter painter(&pm);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(str.toUtf8().constData(), qrcodegen::QrCode::Ecc::LOW);
const int s = qr.getSize()>0?qr.getSize():1;
const double w = pm.width();
const double h = pm.height();
const double aspect = w/h;
const double size = ((aspect>1.0)?h:w);
const double scale = size/(s+2);
const double woff = (w - size) > 0 ? (w - size) / 2 : 0;
const double hoff = (h - size) > 0 ? (h - size) / 2 : 0;
// NOTE: For performance reasons my implementation only draws the foreground parts
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(Qt::black));
for(int y=0; y<s; y++) {
for(int x=0; x<s; x++) {
const int color=qr.getModule(x, y); // 0 for white, 1 for black
if(0!=color) {
const double rx1=(x+1)*scale + woff, ry1=(y+1)*scale + hoff;
QRectF r(rx1, ry1, scale, scale);
painter.drawRects(&r,1);
}
}
}
return pm;
}
void QRCodeLabel::setQrcodeString(QString stra) {
str = stra;
QLabel::setPixmap(scaledPixmap());
}
QString QRCodeLabel::asPngBase64() const {
QByteArray bytes;
QBuffer buffer(&bytes);
buffer.open(QIODevice::WriteOnly);
scaledPixmap().save(&buffer, "PNG");
return bytes.toBase64();
}

25
ui/src/qrcodelabel.h

@ -0,0 +1,25 @@
#ifndef QRCODELABEL_H
#define QRCODELABEL_H
#include "precompiled.h"
class QRCodeLabel : public QLabel
{
Q_OBJECT
public:
explicit QRCodeLabel(QWidget *parent = nullptr);
virtual QSize sizeHint() const;
void setQrcodeString(QString address);
QPixmap scaledPixmap() const;
QString asPngBase64() const;
public slots:
void resizeEvent(QResizeEvent *);
private:
QString str;
};
#endif // QRCODELABEL_H

196
ui/src/ui_mainwindow.h

@ -0,0 +1,196 @@
/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.12.3
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QGroupBox>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QScrollArea>
#include <QtWidgets/QSpacerItem>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_MainWindow
{
public:
QWidget *centralWidget;
QGridLayout *gridLayout;
QGroupBox *Save;
QHBoxLayout *horizontalLayout;
QSpacerItem *horizontalSpacer;
QPushButton *btnSavePDF;
QPushButton *btnSaveJSON;
QGroupBox *Config;
QGridLayout *gridLayout_3;
QLabel *label_3;
QLineEdit *txttaddrs;
QLineEdit *txtEntropy;
QLabel *label;
QLineEdit *txtzaddrs;
QPushButton *btnGenerate;
QLabel *label_2;
QScrollArea *scrollArea;
QWidget *scroll;
QVBoxLayout *verticalLayout;
QMenuBar *menuBar;
QToolBar *mainToolBar;
QStatusBar *statusBar;
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
MainWindow->resize(927, 930);
centralWidget = new QWidget(MainWindow);
centralWidget->setObjectName(QString::fromUtf8("centralWidget"));
gridLayout = new QGridLayout(centralWidget);
gridLayout->setSpacing(6);
gridLayout->setContentsMargins(11, 11, 11, 11);
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
Save = new QGroupBox(centralWidget);
Save->setObjectName(QString::fromUtf8("Save"));
horizontalLayout = new QHBoxLayout(Save);
horizontalLayout->setSpacing(6);
horizontalLayout->setContentsMargins(11, 11, 11, 11);
horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addItem(horizontalSpacer);
btnSavePDF = new QPushButton(Save);
btnSavePDF->setObjectName(QString::fromUtf8("btnSavePDF"));
horizontalLayout->addWidget(btnSavePDF);
btnSaveJSON = new QPushButton(Save);
btnSaveJSON->setObjectName(QString::fromUtf8("btnSaveJSON"));
horizontalLayout->addWidget(btnSaveJSON);
gridLayout->addWidget(Save, 2, 0, 1, 1);
Config = new QGroupBox(centralWidget);
Config->setObjectName(QString::fromUtf8("Config"));
gridLayout_3 = new QGridLayout(Config);
gridLayout_3->setSpacing(6);
gridLayout_3->setContentsMargins(11, 11, 11, 11);
gridLayout_3->setObjectName(QString::fromUtf8("gridLayout_3"));
label_3 = new QLabel(Config);
label_3->setObjectName(QString::fromUtf8("label_3"));
gridLayout_3->addWidget(label_3, 1, 0, 1, 1);
txttaddrs = new QLineEdit(Config);
txttaddrs->setObjectName(QString::fromUtf8("txttaddrs"));
gridLayout_3->addWidget(txttaddrs, 0, 3, 1, 1);
txtEntropy = new QLineEdit(Config);
txtEntropy->setObjectName(QString::fromUtf8("txtEntropy"));
gridLayout_3->addWidget(txtEntropy, 1, 1, 1, 3);
label = new QLabel(Config);
label->setObjectName(QString::fromUtf8("label"));
gridLayout_3->addWidget(label, 0, 0, 1, 1);
txtzaddrs = new QLineEdit(Config);
txtzaddrs->setObjectName(QString::fromUtf8("txtzaddrs"));
gridLayout_3->addWidget(txtzaddrs, 0, 1, 1, 1);
btnGenerate = new QPushButton(Config);
btnGenerate->setObjectName(QString::fromUtf8("btnGenerate"));
gridLayout_3->addWidget(btnGenerate, 2, 0, 1, 1);
label_2 = new QLabel(Config);
label_2->setObjectName(QString::fromUtf8("label_2"));
gridLayout_3->addWidget(label_2, 0, 2, 1, 1);
gridLayout->addWidget(Config, 0, 0, 1, 1);
scrollArea = new QScrollArea(centralWidget);
scrollArea->setObjectName(QString::fromUtf8("scrollArea"));
scrollArea->setStyleSheet(QString::fromUtf8(""));
scrollArea->setWidgetResizable(true);
scroll = new QWidget();
scroll->setObjectName(QString::fromUtf8("scroll"));
scroll->setGeometry(QRect(0, 0, 907, 637));
verticalLayout = new QVBoxLayout(scroll);
verticalLayout->setSpacing(6);
verticalLayout->setContentsMargins(11, 11, 11, 11);
verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
scrollArea->setWidget(scroll);
gridLayout->addWidget(scrollArea, 1, 0, 1, 1);
MainWindow->setCentralWidget(centralWidget);
menuBar = new QMenuBar(MainWindow);
menuBar->setObjectName(QString::fromUtf8("menuBar"));
menuBar->setGeometry(QRect(0, 0, 927, 22));
MainWindow->setMenuBar(menuBar);
mainToolBar = new QToolBar(MainWindow);
mainToolBar->setObjectName(QString::fromUtf8("mainToolBar"));
MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar);
statusBar = new QStatusBar(MainWindow);
statusBar->setObjectName(QString::fromUtf8("statusBar"));
MainWindow->setStatusBar(statusBar);
QWidget::setTabOrder(txtzaddrs, txttaddrs);
QWidget::setTabOrder(txttaddrs, txtEntropy);
QWidget::setTabOrder(txtEntropy, btnGenerate);
QWidget::setTabOrder(btnGenerate, btnSavePDF);
QWidget::setTabOrder(btnSavePDF, btnSaveJSON);
QWidget::setTabOrder(btnSaveJSON, scrollArea);
retranslateUi(MainWindow);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QApplication::translate("MainWindow", "Zec Sapling Paper Wallet", nullptr));
Save->setTitle(QString());
btnSavePDF->setText(QApplication::translate("MainWindow", "Save as PDF", nullptr));
btnSaveJSON->setText(QApplication::translate("MainWindow", "Save as JSON", nullptr));
Config->setTitle(QApplication::translate("MainWindow", "Config", nullptr));
label_3->setText(QApplication::translate("MainWindow", "Additional Entropy", nullptr));
txttaddrs->setText(QApplication::translate("MainWindow", "0", nullptr));
label->setText(QApplication::translate("MainWindow", "Number of z addresses", nullptr));
txtzaddrs->setText(QApplication::translate("MainWindow", "1", nullptr));
btnGenerate->setText(QApplication::translate("MainWindow", "Generate Wallets", nullptr));
label_2->setText(QApplication::translate("MainWindow", "Number of t addresses", nullptr));
} // retranslateUi
};
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_MAINWINDOW_H

144
ui/src/ui_wallet.h

@ -0,0 +1,144 @@
/********************************************************************************
** Form generated from reading UI file 'wallet.ui'
**
** Created by: Qt User Interface Compiler version 5.12.3
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_WALLET_H
#define UI_WALLET_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QFrame>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QSpacerItem>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
#include "qrcodelabel.h"
QT_BEGIN_NAMESPACE
class Ui_WalletWidget
{
public:
QGridLayout *gridLayout;
QFrame *line;
QVBoxLayout *verticalLayout_3;
QSpacerItem *verticalSpacer_3;
QLabel *label_3;
QLabel *lblPrivateKey;
QSpacerItem *verticalSpacer_4;
QVBoxLayout *verticalLayout;
QSpacerItem *verticalSpacer;
QLabel *label_2;
QLabel *lblAddress;
QSpacerItem *verticalSpacer_2;
QRCodeLabel *qrPrivateKey;
QRCodeLabel *qrAddress;
void setupUi(QWidget *WalletWidget)
{
if (WalletWidget->objectName().isEmpty())
WalletWidget->setObjectName(QString::fromUtf8("WalletWidget"));
WalletWidget->resize(847, 533);
gridLayout = new QGridLayout(WalletWidget);
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
line = new QFrame(WalletWidget);
line->setObjectName(QString::fromUtf8("line"));
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
gridLayout->addWidget(line, 1, 0, 1, 4);
verticalLayout_3 = new QVBoxLayout();
verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3"));
verticalSpacer_3 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout_3->addItem(verticalSpacer_3);
label_3 = new QLabel(WalletWidget);
label_3->setObjectName(QString::fromUtf8("label_3"));
label_3->setStyleSheet(QString::fromUtf8("font-weight: bold;"));
verticalLayout_3->addWidget(label_3);
lblPrivateKey = new QLabel(WalletWidget);
lblPrivateKey->setObjectName(QString::fromUtf8("lblPrivateKey"));
lblPrivateKey->setWordWrap(true);
verticalLayout_3->addWidget(lblPrivateKey);
verticalSpacer_4 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout_3->addItem(verticalSpacer_4);
gridLayout->addLayout(verticalLayout_3, 2, 0, 1, 3);
verticalLayout = new QVBoxLayout();
verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout->addItem(verticalSpacer);
label_2 = new QLabel(WalletWidget);
label_2->setObjectName(QString::fromUtf8("label_2"));
label_2->setStyleSheet(QString::fromUtf8("font-weight: bold;"));
label_2->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
verticalLayout->addWidget(label_2);
lblAddress = new QLabel(WalletWidget);
lblAddress->setObjectName(QString::fromUtf8("lblAddress"));
lblAddress->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
lblAddress->setWordWrap(true);
verticalLayout->addWidget(lblAddress);
verticalSpacer_2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout->addItem(verticalSpacer_2);
gridLayout->addLayout(verticalLayout, 0, 1, 1, 3);
qrPrivateKey = new QRCodeLabel(WalletWidget);
qrPrivateKey->setObjectName(QString::fromUtf8("qrPrivateKey"));
gridLayout->addWidget(qrPrivateKey, 2, 3, 1, 1);
qrAddress = new QRCodeLabel(WalletWidget);
qrAddress->setObjectName(QString::fromUtf8("qrAddress"));
qrAddress->setMouseTracking(false);
gridLayout->addWidget(qrAddress, 0, 0, 1, 1);
retranslateUi(WalletWidget);
QMetaObject::connectSlotsByName(WalletWidget);
} // setupUi
void retranslateUi(QWidget *WalletWidget)
{
WalletWidget->setWindowTitle(QApplication::translate("WalletWidget", "Form", nullptr));
label_3->setText(QApplication::translate("WalletWidget", "Private Key", nullptr));
lblPrivateKey->setText(QApplication::translate("WalletWidget", "TextLabel", nullptr));
label_2->setText(QApplication::translate("WalletWidget", "Address", nullptr));
lblAddress->setText(QApplication::translate("WalletWidget", "TextLabel", nullptr));
qrPrivateKey->setText(QApplication::translate("WalletWidget", "TextLabel", nullptr));
qrAddress->setText(QApplication::translate("WalletWidget", "TextLabel", nullptr));
} // retranslateUi
};
namespace Ui {
class WalletWidget: public Ui_WalletWidget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_WALLET_H

158
ui/src/wallet.ui

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WalletWidget</class>
<widget class="QWidget" name="WalletWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>847</width>
<height>533</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="4">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="styleSheet">
<string notr="true">font-weight: bold;</string>
</property>
<property name="text">
<string>Private Key</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblPrivateKey">
<property name="text">
<string>TextLabel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="1" colspan="3">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="styleSheet">
<string notr="true">font-weight: bold;</string>
</property>
<property name="text">
<string>Address</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblAddress">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="3">
<widget class="QRCodeLabel" name="qrPrivateKey">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRCodeLabel" name="qrAddress">
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QRCodeLabel</class>
<extends>QLabel</extends>
<header>qrcodelabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
Loading…
Cancel
Save