diff --git a/Cargo.lock b/Cargo.lock index 6b075b097..5ca418e8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -250,6 +250,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -653,6 +659,19 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "chia-vdf-verify" +version = "0.38.1" +dependencies = [ + "criterion", + "hex", + "malachite-base", + "malachite-nz", + "num-bigint", + "pyo3", + "sha2", +] + [[package]] name = "chia_py_streamable_macro" version = "0.38.1" @@ -907,7 +926,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -928,7 +947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -1184,6 +1203,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1410,6 +1435,15 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash", +] + [[package]] name = "hashlink" version = "0.9.1" @@ -1652,6 +1686,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1736,9 +1779,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsqlite3-sys" @@ -1785,6 +1828,30 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "malachite-base" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8b6f86fdbb1eb9955946be91775239dfcb0acdb1a51bb07d5fc9b8c854f5ccd" +dependencies = [ + "hashbrown 0.16.1", + "itertools 0.14.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-nz" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0197a2f5cfee19d59178e282985c6ca79a9233e26a2adcf40acb693896aa09f6" +dependencies = [ + "itertools 0.14.0", + "libm", + "malachite-base", + "wide", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2519,9 +2586,18 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed" +dependencies = [ + "bytemuck", +] [[package]] name = "same-file" @@ -3133,6 +3209,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wide" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac11b009ebeae802ed758530b6496784ebfee7a87b9abfbcaf3bbe25b814eb25" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 2c316dd22..9dce2bd01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,7 @@ chia-datalayer-macro = { path = "./crates/chia-datalayer/macro", version = "0.38 chia-protocol = { path = "./crates/chia-protocol", version = "0.38.1" } chia-secp = { path = "./crates/chia-secp", version = "0.38.1" } chia-ssl = { path = "./crates/chia-ssl", version = "0.38.1" } +chia-vdf-verify = { path = "./crates/chia-vdf-verify", version = "0.38.1" } chia-traits = { path = "./crates/chia-traits", version = "0.38.1" } chia-puzzle-types = { path = "./crates/chia-puzzle-types", version = "0.38.1" } chia-sha2 = { path = "./crates/chia-sha2", version = "0.38.1" } diff --git a/crates/chia-vdf-verify/Cargo.toml b/crates/chia-vdf-verify/Cargo.toml new file mode 100644 index 000000000..bbb82541e --- /dev/null +++ b/crates/chia-vdf-verify/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "chia-vdf-verify" +version.workspace = true +edition = "2024" +license = "Apache-2.0" +description = "Pure-Rust Chia VDF proof verifier — no GMP, no C dependencies" +authors = ["Richard Kiss "] +homepage = "https://github.com/Chia-Network/chia_rs" +repository = "https://github.com/Chia-Network/chia_rs" + +[lints] +workspace = true + +[lib] +name = "chia_vdf_verify" +crate-type = ["rlib"] + +[dependencies] +malachite-nz = "0.9" +malachite-base = "0.9" +sha2 = { workspace = true } +pyo3 = { workspace = true, features = ["extension-module"], optional = true } + +[features] +python = ["pyo3"] + +[dev-dependencies] +hex = { workspace = true } +criterion = { workspace = true } +num-bigint = { workspace = true } + +[[bench]] +name = "verify" +harness = false + +[[bench]] +name = "bigint_compare" +harness = false diff --git a/crates/chia-vdf-verify/benches/bigint_compare.rs b/crates/chia-vdf-verify/benches/bigint_compare.rs new file mode 100644 index 000000000..2f1a2503d --- /dev/null +++ b/crates/chia-vdf-verify/benches/bigint_compare.rs @@ -0,0 +1,240 @@ +/// Benchmark: num-bigint vs malachite-nz for operations relevant to VDF verification. +/// +/// Operations tested: +/// - modpow (used in hash_prime / Miller-Rabin) +/// - extended_gcd (used in nucomp / nudupl) +/// - multiply / divide (used throughout) +/// +/// Run with: +/// cargo bench --bench bigint_compare +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use malachite_base::num::arithmetic::traits::{ExtendedGcd, ModPow}; +use malachite_base::num::basic::traits::Zero; +use malachite_nz::integer::Integer as MalachiteInteger; +use malachite_nz::natural::Natural; +use num_bigint::BigUint; + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +fn nat_from_be_bytes(bytes: &[u8]) -> Natural { + let mut n = Natural::ZERO; + for &b in bytes { + n <<= 8u64; + n += Natural::from(b); + } + n +} + +fn mal_int_from_be_bytes(bytes: &[u8]) -> MalachiteInteger { + MalachiteInteger::from(nat_from_be_bytes(bytes)) +} + +fn biguint_from_be_bytes(bytes: &[u8]) -> BigUint { + BigUint::from_bytes_be(bytes) +} + +/// Build (base, exponent, modulus) at `bits` bit-size as pseudo-random byte patterns. +fn make_numbers( + bits: usize, +) -> ( + BigUint, + BigUint, + BigUint, + Natural, + Natural, + Natural, + MalachiteInteger, + MalachiteInteger, +) { + let byte_len = bits / 8; + let base_bytes: Vec = (0..byte_len) + .map(|i| ((i.wrapping_mul(97).wrapping_add(17)) & 0xff) as u8) + .collect(); + let exp_bytes: Vec = (0..byte_len) + .map(|i| ((i.wrapping_mul(131).wrapping_add(37)) & 0xff) as u8) + .collect(); + let mut mod_bytes: Vec = (0..byte_len) + .map(|i| ((i.wrapping_mul(173).wrapping_add(53)) & 0xff) as u8) + .collect(); + mod_bytes[0] |= 0x80; + mod_bytes[byte_len - 1] |= 0x01; + + let b_base = biguint_from_be_bytes(&base_bytes); + let b_exp = biguint_from_be_bytes(&exp_bytes); + let b_mod = biguint_from_be_bytes(&mod_bytes); + let b_base_r = &b_base % &b_mod; + + let m_base = nat_from_be_bytes(&base_bytes); + let m_exp = nat_from_be_bytes(&exp_bytes); + let m_mod = nat_from_be_bytes(&mod_bytes); + let m_base_r = &m_base % &m_mod; + + let mi_a = mal_int_from_be_bytes(&base_bytes); + let mi_b = mal_int_from_be_bytes(&exp_bytes); + + (b_base_r, b_exp, b_mod, m_base_r, m_exp, m_mod, mi_a, mi_b) +} + +// ── modpow ───────────────────────────────────────────────────────────────────── + +fn bench_modpow(c: &mut Criterion) { + let mut group = c.benchmark_group("modpow"); + + for bits in [256usize, 512, 1024] { + let (b_base, b_exp, b_mod, m_base, m_exp, m_mod, _, _) = make_numbers(bits); + + group.bench_with_input(BenchmarkId::new("num-bigint", bits), &bits, |b, _| { + b.iter(|| { + black_box(b_base.modpow(&b_exp, &b_mod)); + }); + }); + + group.bench_with_input(BenchmarkId::new("malachite", bits), &bits, |b, _| { + b.iter(|| { + black_box((&m_base).mod_pow(&m_exp, &m_mod)); + }); + }); + } + + group.finish(); +} + +// ── extended_gcd ────────────────────────────────────────────────────────────── + +fn bench_extended_gcd(c: &mut Criterion) { + let mut group = c.benchmark_group("extended_gcd"); + + for bits in [256usize, 512, 1024] { + let byte_len = bits / 8; + let a_bytes: Vec = (0..byte_len) + .map(|i| ((i.wrapping_mul(199).wrapping_add(7)) & 0xff) as u8) + .collect(); + let mut b_bytes: Vec = (0..byte_len) + .map(|i| ((i.wrapping_mul(251).wrapping_add(13)) & 0xff) as u8) + .collect(); + b_bytes[0] |= 0x80; + b_bytes[byte_len - 1] |= 0x01; + + let mal_a = mal_int_from_be_bytes(&a_bytes); + let mal_b = mal_int_from_be_bytes(&b_bytes); + + // num-bigint fast_extended_gcd now takes malachite Integer (since the lib is ported) + group.bench_with_input( + BenchmarkId::new("malachite (built-in xgcd)", bits), + &bits, + |b, _| { + b.iter(|| { + black_box(chia_vdf_verify::integer::fast_extended_gcd(&mal_a, &mal_b)); + }); + }, + ); + + let mal_a2 = nat_from_be_bytes(&a_bytes); + let mal_b2 = nat_from_be_bytes(&b_bytes); + + group.bench_with_input( + BenchmarkId::new("malachite (Natural xgcd)", bits), + &bits, + |b, _| { + b.iter(|| { + black_box(mal_a2.clone().extended_gcd(mal_b2.clone())); + }); + }, + ); + } + + group.finish(); +} + +// ── multiply ────────────────────────────────────────────────────────────────── + +fn bench_multiply(c: &mut Criterion) { + let mut group = c.benchmark_group("multiply"); + + for bits in [256usize, 512, 1024] { + let (b_base, b_exp, _b_mod, m_base, m_exp, _m_mod, _, _) = make_numbers(bits); + + group.bench_with_input(BenchmarkId::new("num-bigint", bits), &bits, |b, _| { + b.iter(|| { + black_box(&b_base * &b_exp); + }); + }); + + group.bench_with_input(BenchmarkId::new("malachite", bits), &bits, |b, _| { + b.iter(|| { + black_box(&m_base * &m_exp); + }); + }); + } + + group.finish(); +} + +// ── divide ──────────────────────────────────────────────────────────────────── + +fn bench_divide(c: &mut Criterion) { + let mut group = c.benchmark_group("divide"); + + for bits in [256usize, 512, 1024] { + let (b_base, b_exp, _b_mod, m_base, m_exp, _m_mod, _, _) = make_numbers(bits); + let b_dividend = &b_base * &b_exp; + let m_dividend = &m_base * &m_exp; + + group.bench_with_input(BenchmarkId::new("num-bigint", bits), &bits, |b, _| { + b.iter(|| { + black_box(&b_dividend / &b_exp); + }); + }); + + group.bench_with_input(BenchmarkId::new("malachite", bits), &bits, |b, _| { + b.iter(|| { + black_box(&m_dividend / &m_exp); + }); + }); + } + + group.finish(); +} + +// ── full VDF verify ─────────────────────────────────────────────────────────── + +fn bench_full_verify(c: &mut Criterion) { + use chia_vdf_verify::integer::from_bytes_be; + use chia_vdf_verify::verifier::check_proof_of_time_n_wesolowski; + + fn hex_decode(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() + } + + const D1_HEX: &str = "d0cb181074454b32a0e0fc5e65a1d7625ea43756eaa8de13a9c750c79f7aa60151f065cd5775516159c28713c1e74ced6520f8f5c55129f32f865b28cf7fe8e7"; + const X_HEX: &str = "08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + const P1_HEX: &str = "020020417eb39c4e14954a817af644fc13d086c26dddab8afea12415b5e685f7883f5740ba01cb75220081c8aba7854cbd52010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + let d1: MalachiteInteger = -from_bytes_be(&hex_decode(D1_HEX)); + let x_s = hex_decode(X_HEX); + let p1 = hex_decode(P1_HEX); + + let mut group = c.benchmark_group("full_verify"); + group.bench_function("malachite/iters=100", |b| { + b.iter(|| { + let result = check_proof_of_time_n_wesolowski(&d1, &x_s, &p1, 100, 0); + assert!(result); + }); + }); + group.finish(); +} + +// ── entry point ─────────────────────────────────────────────────────────────── + +criterion_group!( + benches, + bench_modpow, + bench_extended_gcd, + bench_multiply, + bench_divide, + bench_full_verify, +); +criterion_main!(benches); diff --git a/crates/chia-vdf-verify/benches/verify.rs b/crates/chia-vdf-verify/benches/verify.rs new file mode 100644 index 000000000..4f0a134ce --- /dev/null +++ b/crates/chia-vdf-verify/benches/verify.rs @@ -0,0 +1,57 @@ +use chia_vdf_verify::integer::from_bytes_be; +use chia_vdf_verify::verifier::check_proof_of_time_n_wesolowski; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use malachite_nz::integer::Integer; + +fn hex_decode(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() +} + +// Test vector 1: seed="test_seed_chia", disc=512, iters=100 +const D1_HEX: &str = "d0cb181074454b32a0e0fc5e65a1d7625ea43756eaa8de13a9c750c79f7aa60151f065cd5775516159c28713c1e74ced6520f8f5c55129f32f865b28cf7fe8e7"; +const X_HEX: &str = "08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; +const P1_HEX: &str = "020020417eb39c4e14954a817af644fc13d086c26dddab8afea12415b5e685f7883f5740ba01cb75220081c8aba7854cbd52010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + +// Test vector 2: seed="chia-vdf-rust", disc=512, iters=200 +const D2_HEX: &str = "c3ef34d02017540ef26d88057bbfc778da12ed572b99f8707834ed344577c210b1f9287f54a536913177bf5880a4a51b6bfa42445f3fbcd082b695e38c2066d7"; +const P2_HEX: &str = "030033205ea6d1ab367757073029f1462eb2fcc79749871d0b576f7a392adac84f56f46100e477d59353376f82a3eb56720d010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + +fn bench_verify(c: &mut Criterion) { + let d1: Integer = -from_bytes_be(&hex_decode(D1_HEX)); + let d2: Integer = -from_bytes_be(&hex_decode(D2_HEX)); + let x_s = hex_decode(X_HEX); + let p1 = hex_decode(P1_HEX); + let p2 = hex_decode(P2_HEX); + + let mut group = c.benchmark_group("verify"); + + group.bench_with_input( + BenchmarkId::new("iters", 100), + &(&d1, &x_s, &p1, 100u64), + |b, (d, x, p, iters)| { + b.iter(|| { + let result = check_proof_of_time_n_wesolowski(d, x, p, *iters, 0); + assert!(result); + }); + }, + ); + + group.bench_with_input( + BenchmarkId::new("iters", 200), + &(&d2, &x_s, &p2, 200u64), + |b, (d, x, p, iters)| { + b.iter(|| { + let result = check_proof_of_time_n_wesolowski(d, x, p, *iters, 0); + assert!(result); + }); + }, + ); + + group.finish(); +} + +criterion_group!(benches, bench_verify); +criterion_main!(benches); diff --git a/crates/chia-vdf-verify/src/bin/timing.rs b/crates/chia-vdf-verify/src/bin/timing.rs new file mode 100644 index 000000000..d310194fd --- /dev/null +++ b/crates/chia-vdf-verify/src/bin/timing.rs @@ -0,0 +1,130 @@ +use std::time::Instant; + +fn time_fn(name: &str, n: u32, f: F) { + for _ in 0..3 { + f(); + } + let t0 = Instant::now(); + for _ in 0..n { + f(); + } + let elapsed = t0.elapsed(); + println!( + "{:<45} {:>8.1} µs/call (n={})", + name, + elapsed.as_micros() as f64 / n as f64, + n + ); +} + +fn main() { + use chia_vdf_verify::*; + use malachite_nz::integer::Integer; + + fn hex_decode(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() + } + + let d = -integer::from_bytes_be(&hex_decode("d0cb181074454b32a0e0fc5e65a1d7625ea43756eaa8de13a9c750c79f7aa60151f065cd5775516159c28713c1e74ced6520f8f5c55129f32f865b28cf7fe8e7")); + let x_s = hex_decode("08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let p_blob = hex_decode("020020417eb39c4e14954a817af644fc13d086c26dddab8afea12415b5e685f7883f5740ba01cb75220081c8aba7854cbd52010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + + let x = proof_common::deserialize_form(&d, &x_s).unwrap(); + let y = proof_common::deserialize_form(&d, &p_blob[..bqfc::BQFC_FORM_SIZE]).unwrap(); + let mut xm = x.clone(); + let mut ym = y.clone(); + let b = proof_common::get_b(&d, &mut xm, &mut ym); + let l = form::Form::compute_l(&d); + let r = proof_common::fast_pow(100, &b); + + println!("=== Timing breakdown (512-bit disc, iters=100) ==="); + println!( + "B bits = {}, r bits = {}", + integer::num_bits(&b), + integer::num_bits(&r) + ); + + time_fn("full verify (iters=100)", 300, || { + assert!(verifier::check_proof_of_time_n_wesolowski( + &d, &x_s, &p_blob, 100, 0 + )); + }); + + time_fn("get_b (2x hash_prime + serialize)", 1000, || { + let mut xm = x.clone(); + let mut ym = y.clone(); + std::hint::black_box(proof_common::get_b(&d, &mut xm, &mut ym)); + }); + + let proof = proof_common::deserialize_form(&d, &p_blob[bqfc::BQFC_FORM_SIZE..]).unwrap(); + time_fn("fast_pow_form(proof, B=264-bit)", 300, || { + std::hint::black_box(proof_common::fast_pow_form_nucomp(&proof, &d, &b, &l)); + }); + + time_fn("fast_pow_form(x, r=small)", 10000, || { + std::hint::black_box(proof_common::fast_pow_form_nucomp(&x, &d, &r, &l)); + }); + + time_fn("single nudupl + reduce", 20000, || { + let f = nucomp::nudupl(&x, &d, &l); + let mut f = f; + reducer::reduce(&mut f); + std::hint::black_box(f); + }); + + time_fn("single nucomp(x,y) + reduce", 20000, || { + let mut f = nucomp::nucomp(&x, &y, &d, &l); + reducer::reduce(&mut f); + std::hint::black_box(f); + }); + + time_fn("reducer::reduce (no-op, already reduced)", 100000, || { + let mut f = x.clone(); + reducer::reduce(&mut f); + std::hint::black_box(f); + }); + + let d_abs = Integer::from(d.unsigned_abs_ref().clone()); + time_fn("xgcd_partial (512-bit r2, 264-bit r1)", 20000, || { + let mut co2 = Integer::from(0i32); + let mut co1 = Integer::from(0i32); + let mut r2 = d_abs.clone(); + let mut r1 = b.clone(); + xgcd_partial::xgcd_partial(&mut co2, &mut co1, &mut r2, &mut r1, &l); + std::hint::black_box((co2, co1)); + }); + + time_fn("get_si_2exp (512-bit)", 500000, || { + std::hint::black_box(integer::get_si_2exp(&d)); + }); + + time_fn("hash_prime (264 bits)", 200, || { + std::hint::black_box(primetest::hash_prime(b"timing_seed_12345", 264, &[263])); + }); + + time_fn("Integer mul (512x512 -> 1024)", 500000, || { + let r = &d * &d; + std::hint::black_box(r); + }); + + let half_d = &d >> 1u64; + let half_d_abs = Integer::from(half_d.unsigned_abs_ref().clone()); + + time_fn("fast_extended_gcd malachite (512-bit)", 50000, || { + let r = chia_vdf_verify::integer::fast_extended_gcd(&d_abs, &b); + std::hint::black_box(r); + }); + + time_fn("fast_extended_gcd malachite (256-bit)", 50000, || { + let r = chia_vdf_verify::integer::fast_extended_gcd(&half_d_abs, &b); + std::hint::black_box(r); + }); + + time_fn("fast_gcd_coeff_b malachite (256-bit)", 50000, || { + let r = chia_vdf_verify::integer::fast_gcd_coeff_b(&half_d_abs, &b); + std::hint::black_box(r); + }); +} diff --git a/crates/chia-vdf-verify/src/bqfc.rs b/crates/chia-vdf-verify/src/bqfc.rs new file mode 100644 index 000000000..028fb6d9a --- /dev/null +++ b/crates/chia-vdf-verify/src/bqfc.rs @@ -0,0 +1,303 @@ +//! BQFC compressed form serialization/deserialization. +//! +//! Port of chiavdf/src/bqfc.c and bqfc.h. +//! +//! Format (100 bytes for 1024-bit discriminant): +//! Byte 0: flags (b_sign, t_sign, is_identity, is_generator) +//! Byte 1: g_size (size of 'g' in bytes minus 1) +//! d/16 - g_size bytes: a' = a/g +//! d/32 - g_size bytes: t' = t/g +//! g_size+1 bytes: g +//! g_size+1 bytes: b0 + +use crate::integer::{ + divexact, fdiv_r, from_bytes_le, gcd_nonneg, isqrt, num_bits, tdiv_q, to_bytes_le_padded, +}; +use crate::xgcd_partial::xgcd_partial; +use malachite_base::num::arithmetic::traits::{ExtendedGcd, FloorSqrt, ModPow}; +use malachite_base::num::basic::traits::{One, Zero}; +use malachite_base::num::logic::traits::SignificantBits; +use malachite_nz::integer::Integer; +use malachite_nz::natural::Natural; + +/// Size of the serialized form (100 bytes for 1024-bit max discriminant). +pub const BQFC_FORM_SIZE: usize = 1024_usize.div_ceil(32) * 3 + 4; + +/// Flag bits +const BQFC_B_SIGN: u8 = 1 << 0; +const BQFC_T_SIGN: u8 = 1 << 1; +const BQFC_IS_1: u8 = 1 << 2; +const BQFC_IS_GEN: u8 = 1 << 3; + +/// Compressed form intermediate representation. +struct QfbC { + a: Integer, + t: Integer, + g: Integer, + b0: Integer, + b_sign: bool, +} + +/// Compress (a, b) to intermediate representation. +fn bqfc_compr(a: &Integer, b: &Integer) -> QfbC { + if a == b { + return QfbC { + a: a.clone(), + t: Integer::ZERO, + g: Integer::ZERO, + b0: Integer::ZERO, + b_sign: false, + }; + } + + let sign = *b < 0i32; + let a_sqrt = isqrt(a); + + let mut a_copy = a.clone(); + let mut b_copy = if sign { -b } else { b.clone() }; + + let mut dummy = Integer::ZERO; + let mut t = Integer::ZERO; + + xgcd_partial(&mut dummy, &mut t, &mut a_copy, &mut b_copy, &a_sqrt); + t = -t; + + let g = gcd_nonneg(a, &t); + + let (a_out, b0) = if g == 1i32 { + (a.clone(), Integer::ZERO) + } else { + let a_new = divexact(a, &g); + let t_new = divexact(&t, &g); + let b0 = tdiv_q(b, &a_new); + let b0 = if sign { -b0 } else { b0 }; + t = t_new; + (a_new, b0) + }; + + QfbC { + a: a_out, + t, + g, + b_sign: sign, + b0, + } +} + +/// Decompress intermediate representation to (a, b) given discriminant D. +fn bqfc_decompr(d: &Integer, c: &QfbC) -> Result<(Integer, Integer), String> { + if c.t == 0i32 { + return Ok((c.a.clone(), c.a.clone())); + } + + let t = if c.t < 0i32 { &c.t + &c.a } else { c.t.clone() }; + + if c.a == 0i32 { + return Err("bqfc_decompr: a is zero".to_string()); + } + + // Compute modular inverse of t mod a using extended GCD + let t_nat = t.unsigned_abs_ref().clone(); + let a_nat = c.a.unsigned_abs_ref().clone(); + let (gcd, x, _y) = t_nat.extended_gcd(a_nat.clone()); + if gcd != Natural::ONE { + return Err(format!("bqfc_decompr: gcd(t, a) = {} != 1", gcd)); + } + let t_inv = if x < Integer::ZERO { x + &c.a } else { x }; + + let d_mod_a = fdiv_r(d, &c.a); + + // t^2 mod a — squaring is sign-agnostic, use |c.t| + let ct_nat = c.t.unsigned_abs_ref().clone(); + let a_nat2 = c.a.unsigned_abs_ref().clone(); + let t_sq_mod = Integer::from(ct_nat.mod_pow(Natural::from(2u32), a_nat2)); + + let tmp_prod = &t_sq_mod * &d_mod_a; + let tmp_mod = &tmp_prod % &c.a; + + let tmp_mod_nat = tmp_mod.unsigned_abs_ref().clone(); + let tmp_sqrt = Integer::from((&tmp_mod_nat).floor_sqrt()); + if &tmp_sqrt * &tmp_sqrt != tmp_mod { + return Err("bqfc_decompr: not a perfect square".to_string()); + } + + let out_b = (&tmp_sqrt * &t_inv) % &c.a; + + let out_a = if c.g > 1i32 { &c.a * &c.g } else { c.a.clone() }; + + let out_b = if c.b0 > 0i32 { + out_b + &c.a * &c.b0 + } else { + out_b + }; + + let out_b = if c.b_sign { -out_b } else { out_b }; + + Ok((out_a, out_b)) +} + +/// Export n as little-endian bytes of exactly `size` bytes. +fn export_le(out: &mut Vec, n: &Integer, size: usize) { + let bytes = to_bytes_le_padded(n, size); + out.extend_from_slice(&bytes); +} + +/// Serialize (a, b) with discriminant bit length d_bits to 100-byte output. +pub fn serialize(a: &Integer, b: &Integer, d_bits: usize) -> Vec { + let mut out = vec![0u8; BQFC_FORM_SIZE]; + + if *b == 1i32 && *a <= 2i32 { + out[0] = if *a == 2i32 { BQFC_IS_GEN } else { BQFC_IS_1 }; + return out; + } + + let d_bits_rounded = (d_bits + 31) & !31usize; + let c = bqfc_compr(a, b); + let valid_size = bqfc_get_compr_size(d_bits); + + let mut buf = Vec::with_capacity(valid_size); + let mut flags = 0u8; + if c.b_sign { + flags |= BQFC_B_SIGN; + } + if c.t < 0i32 { + flags |= BQFC_T_SIGN; + } + + let g_size = if c.g == 0i32 { + 0usize + } else { + let bits = c.g.significant_bits() as usize; + bits.div_ceil(8) + }; + let g_size = if g_size == 0 { 0 } else { g_size - 1 }; + + buf.push(flags); + buf.push(g_size as u8); + + export_le(&mut buf, &c.a, d_bits_rounded / 16 - g_size); + let t_abs = if c.t < 0i32 { + -c.t.clone() + } else { + c.t.clone() + }; + export_le(&mut buf, &t_abs, d_bits_rounded / 32 - g_size); + export_le(&mut buf, &c.g, g_size + 1); + export_le(&mut buf, &c.b0, g_size + 1); + + let copy_len = buf.len().min(BQFC_FORM_SIZE); + out[..copy_len].copy_from_slice(&buf[..copy_len]); + out +} + +/// Deserialize a form from 100-byte input with discriminant D. +/// Returns (a, b) or error string. +pub fn deserialize(d: &Integer, data: &[u8]) -> Result<(Integer, Integer), String> { + if data.len() != BQFC_FORM_SIZE { + return Err(format!( + "expected {} bytes, got {}", + BQFC_FORM_SIZE, + data.len() + )); + } + + if data[0] & (BQFC_IS_1 | BQFC_IS_GEN) != 0 { + let a = if data[0] & BQFC_IS_GEN != 0 { + Integer::from(2u32) + } else { + Integer::ONE + }; + return Ok((a, Integer::ONE)); + } + + let d_bits = num_bits(d); + let d_bits_rounded = (d_bits + 31) & !31usize; + + let g_size = data[1] as usize; + if g_size >= d_bits_rounded / 32 { + return Err("g_size out of range".to_string()); + } + + let mut offset = 2usize; + + let a_bytes = d_bits_rounded / 16 - g_size; + let a = from_bytes_le(&data[offset..offset + a_bytes]); + offset += a_bytes; + + let t_bytes = d_bits_rounded / 32 - g_size; + let t_raw = from_bytes_le(&data[offset..offset + t_bytes]); + offset += t_bytes; + + let g = from_bytes_le(&data[offset..offset + g_size + 1]); + offset += g_size + 1; + + let b0 = from_bytes_le(&data[offset..offset + g_size + 1]); + + let b_sign = data[0] & BQFC_B_SIGN != 0; + let t_sign = data[0] & BQFC_T_SIGN != 0; + let t = if t_sign { -t_raw } else { t_raw }; + + let c = QfbC { + a, + t, + g, + b0, + b_sign, + }; + + let (out_a, out_b) = bqfc_decompr(d, &c)?; + + let canon = serialize(&out_a, &out_b, d_bits); + if canon != data { + return Err("non-canonical serialization".to_string()); + } + + Ok((out_a, out_b)) +} + +/// Compute the serialization size for a given discriminant bit length. +pub fn bqfc_get_compr_size(d_bits: usize) -> usize { + let d_bits_rounded = (d_bits + 31) & !31usize; + d_bits_rounded / 32 * 3 + 4 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bqfc_form_size() { + assert_eq!(bqfc_get_compr_size(1024), 100); + assert_eq!(BQFC_FORM_SIZE, 100); + } + + #[test] + fn test_serialize_identity() { + let d = Integer::from(-47i64); + let a = Integer::ONE; + let b = Integer::ONE; + let data = serialize(&a, &b, num_bits(&d)); + assert_eq!(data[0], BQFC_IS_1); + } + + #[test] + fn test_serialize_generator() { + let d = Integer::from(-47i64); + let a = Integer::from(2i32); + let b = Integer::ONE; + let data = serialize(&a, &b, num_bits(&d)); + assert_eq!(data[0], BQFC_IS_GEN); + } + + #[test] + fn test_bqfc_roundtrip_identity() { + let d = Integer::from(-47i64); + let d_bits = num_bits(&d); + let a = Integer::ONE; + let b = Integer::ONE; + let data = serialize(&a, &b, d_bits); + let (ra, rb) = deserialize(&d, &data).unwrap(); + assert_eq!(ra, a); + assert_eq!(rb, b); + } +} diff --git a/crates/chia-vdf-verify/src/discriminant.rs b/crates/chia-vdf-verify/src/discriminant.rs new file mode 100644 index 000000000..f36a07ce2 --- /dev/null +++ b/crates/chia-vdf-verify/src/discriminant.rs @@ -0,0 +1,51 @@ +//! CreateDiscriminant: generate a negative discriminant from a seed. +//! +//! Port of chiavdf/src/create_discriminant.h. + +use crate::primetest::hash_prime; +use malachite_nz::integer::Integer; + +/// Create a discriminant D from a seed and bit length. +/// D = -HashPrime(seed, length, {0, 1, 2, length-1}) +/// D ≡ 7 (mod 8), so D ≡ 1 (mod 8) after negation. +pub fn create_discriminant(seed: &[u8], length: usize) -> Integer { + assert!( + length > 0 && length.is_multiple_of(8), + "length must be positive multiple of 8" + ); + let bitmask = vec![0usize, 1, 2, length - 1]; + let p = hash_prime(seed, length, &bitmask); + -p +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::integer::fdiv_r; + use crate::primetest::is_prime_bpsw; + + #[test] + fn test_discriminant_is_negative() { + let seed = b"test_seed"; + let d = create_discriminant(seed, 512); + assert!(d < 0i32, "discriminant should be negative"); + } + + #[test] + fn test_discriminant_mod8() { + let seed = b"test_seed"; + let d = create_discriminant(seed, 512); + let r = fdiv_r(&d, &Integer::from(8i32)); + assert_eq!(r, Integer::from(1i32), "discriminant should be ≡ 1 mod 8"); + } + + #[test] + fn test_discriminant_is_prime_magnitude() { + let seed = b"small_test"; + let d = create_discriminant(seed, 256); + assert!( + is_prime_bpsw(&(-d)), + "discriminant magnitude should be prime" + ); + } +} diff --git a/crates/chia-vdf-verify/src/form.rs b/crates/chia-vdf-verify/src/form.rs new file mode 100644 index 000000000..ea6ec5fea --- /dev/null +++ b/crates/chia-vdf-verify/src/form.rs @@ -0,0 +1,116 @@ +//! Quadratic form (a, b, c) with discriminant D, where b^2 - 4ac = D. +//! Always in reduced form: |b| <= a <= c, with b >= 0 when a == c or |b| == a. + +use crate::integer::{divexact, num_bits}; +use crate::reducer::reduce; +use malachite_base::num::basic::traits::One; +use malachite_nz::integer::Integer; + +/// A binary quadratic form with coefficients (a, b, c) and discriminant D. +#[derive(Clone, Debug)] +pub struct Form { + pub a: Integer, + pub b: Integer, + pub c: Integer, +} + +impl Form { + pub fn new(a: Integer, b: Integer, c: Integer) -> Self { + Form { a, b, c } + } + + /// Construct form from (a, b) and discriminant D, computing c = (b^2 - D) / (4a). + pub fn from_abd(a: Integer, b: Integer, d: &Integer) -> Self { + let b2 = &b * &b; + let num = b2 - d; + let denom = Integer::from(4i32) * &a; + let c = divexact(&num, &denom); + Form { a, b, c } + } + + /// Identity form: (1, 1, (1-D)/4). + pub fn identity(d: &Integer) -> Self { + let a = Integer::ONE; + let b = Integer::ONE; + let num = Integer::ONE - d; + let c = divexact(&num, &Integer::from(4i32)); + Form { a, b, c } + } + + /// Generator form: (2, 1, (1-D)/8) — only valid when D ≡ 1 mod 8. + pub fn generator(d: &Integer) -> Self { + let a = Integer::from(2i32); + let b = Integer::ONE; + let num = Integer::ONE - d; + let c = divexact(&num, &Integer::from(8i32)); + Form { a, b, c } + } + + /// Check if this is the identity form (a=1, b=1). + pub fn is_identity(&self) -> bool { + self.a == 1i32 && self.b == 1i32 + } + + /// Check if this is the generator form (a=2, b=1). + pub fn is_generator(&self) -> bool { + self.a == 2i32 && self.b == 1i32 + } + + /// Check if this form is reduced: |b| <= a <= c, with b >= 0 when a == c or |b| == a. + pub fn is_reduced(&self) -> bool { + let abs_b = self.b.unsigned_abs_ref().clone(); + let abs_a = self.a.unsigned_abs_ref().clone(); + let abs_c = self.c.unsigned_abs_ref().clone(); + if abs_b > abs_a { + return false; + } + if abs_a > abs_c { + return false; + } + if self.a == self.c && self.b < 0i32 { + return false; + } + if abs_b == abs_a && self.b < 0i32 { + return false; + } + true + } + + /// Reduce this form in place using the Pulmark reducer. + pub fn reduce(&mut self) { + reduce(self); + } + + /// The half-max size parameter L = floor((-D)^(1/4)). + /// Used as a threshold in nucomp. + pub fn compute_l(d: &Integer) -> Integer { + let neg_d = d.unsigned_abs_ref().clone(); + crate::integer::nth_root(&Integer::from(neg_d), 4) + } + + /// Discriminant size in bits. + pub fn d_bits(d: &Integer) -> usize { + num_bits(d) + } +} + +impl PartialEq for Form { + fn eq(&self, other: &Self) -> bool { + self.a == other.a && self.b == other.b && self.c == other.c + } +} + +impl Eq for Form {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_identity_form() { + let d = Integer::from(-47i64); + let f = Form::identity(&d); + let disc = &f.b * &f.b - Integer::from(4i32) * &f.a * &f.c; + assert_eq!(disc, d, "identity form discriminant check"); + } +} diff --git a/crates/chia-vdf-verify/src/integer.rs b/crates/chia-vdf-verify/src/integer.rs new file mode 100644 index 000000000..b1e8b1463 --- /dev/null +++ b/crates/chia-vdf-verify/src/integer.rs @@ -0,0 +1,300 @@ +//! BigInt wrapper using malachite-nz with GMP-compatible operations. + +use malachite_base::num::arithmetic::traits::{ + DivExact, DivMod, ExtendedGcd, FloorRoot, FloorSqrt, Gcd, JacobiSymbol, ModPow, Parity, +}; +use malachite_base::num::basic::traits::Zero; +use malachite_base::num::logic::traits::SignificantBits; +use malachite_nz::integer::Integer; +use malachite_nz::natural::Natural; + +pub use malachite_nz::integer::Integer as Int; + +/// Number of bits in a machine limb (for Lehmer extraction). +pub const LIMB_BITS: usize = 64; + +/// Compute floor division (matching GMP mpz_fdiv_q behavior). +/// GMP floors toward -infinity; malachite's DivMod also floors. +pub fn fdiv_q(a: &Integer, b: &Integer) -> Integer { + a.div_mod(b).0 +} + +/// Compute floor remainder (matching GMP mpz_fdiv_r). +/// The remainder has the same sign as the divisor. +pub fn fdiv_r(a: &Integer, b: &Integer) -> Integer { + a.div_mod(b).1 +} + +/// Compute truncating division (matches Rust's default `/`). +pub fn tdiv_q(a: &Integer, b: &Integer) -> Integer { + a / b +} + +/// Compute truncating remainder (matches Rust's default `%`). +pub fn tdiv_r(a: &Integer, b: &Integer) -> Integer { + a % b +} + +/// Exact division — panics in debug if remainder != 0. +pub fn divexact(a: &Integer, b: &Integer) -> Integer { + a.div_exact(b) +} + +/// Integer square root (floor). Argument must be non-negative. +pub fn isqrt(n: &Integer) -> Integer { + debug_assert!(*n >= 0i32, "isqrt: negative argument"); + Integer::from(n.unsigned_abs_ref().floor_sqrt()) +} + +/// nth root (floor). Argument must be non-negative. +pub fn nth_root(n: &Integer, k: u32) -> Integer { + debug_assert!(*n >= 0i32, "nth_root: negative argument"); + Integer::from(n.unsigned_abs_ref().floor_root(k as u64)) +} + +/// Jacobi symbol (a/n). n must be odd and positive. +pub fn jacobi(a: &Integer, n: &Integer) -> i32 { + debug_assert!(*n > 0i32, "n must be > 1, got {}", n); + debug_assert!(n.odd(), "n must be odd, got {}", n); + a.jacobi_symbol(n) as i32 +} + +/// modpow: base^exp mod modulus. base, exp, and modulus should be non-negative. +pub fn modpow(base: &Integer, exp: &Integer, modulus: &Integer) -> Integer { + debug_assert!(*exp >= 0i32, "modpow: negative exponent"); + debug_assert!(*modulus > 0i32, "modpow: non-positive modulus"); + let base_nat = base.unsigned_abs_ref(); + let exp_nat = exp.unsigned_abs_ref(); + let mod_nat = modulus.unsigned_abs_ref(); + Integer::from(base_nat.clone().mod_pow(exp_nat.clone(), mod_nat)) +} + +/// Import big-endian bytes as a non-negative integer. +pub fn from_bytes_be(bytes: &[u8]) -> Integer { + let mut n = Natural::ZERO; + for &b in bytes { + n <<= 8u64; + n += Natural::from(b); + } + Integer::from(n) +} + +/// Export as big-endian bytes with the given byte length (zero-padded on left). +pub fn to_bytes_be_padded(n: &Integer, len: usize) -> Vec { + let bytes = nat_to_bytes_be(n.unsigned_abs_ref()); + if bytes.len() >= len { + bytes[bytes.len() - len..].to_vec() + } else { + let mut out = vec![0u8; len]; + out[len - bytes.len()..].copy_from_slice(&bytes); + out + } +} + +/// Import little-endian bytes as a non-negative integer. +pub fn from_bytes_le(bytes: &[u8]) -> Integer { + let mut n = Natural::ZERO; + for (i, &b) in bytes.iter().enumerate() { + n += Natural::from(b) << (i as u64 * 8); + } + Integer::from(n) +} + +/// Export as little-endian bytes with the given byte length (zero-padded on right). +pub fn to_bytes_le_padded(n: &Integer, len: usize) -> Vec { + let nat = n.unsigned_abs_ref(); + let limbs = nat.as_limbs_asc(); + let mut out = vec![0u8; len]; + let mut pos = 0usize; + 'outer: for &limb in limbs { + for i in 0..8 { + if pos >= len { + break 'outer; + } + out[pos] = (limb >> (i * 8)) as u8; + pos += 1; + } + } + out +} + +/// Convert Natural to big-endian byte vector (no leading zeros except for zero itself). +fn nat_to_bytes_be(n: &Natural) -> Vec { + let limbs = n.to_limbs_desc(); // most-significant limb first + if limbs.is_empty() { + return vec![]; + } + let mut out = Vec::with_capacity(limbs.len() * 8); + let mut leading = true; + for limb in limbs { + for i in (0..8).rev() { + let byte = (limb >> (i * 8)) as u8; + if leading && byte == 0 { + continue; + } + leading = false; + out.push(byte); + } + } + out +} + +/// Number of bits needed to represent the absolute value (minimum 1). +pub fn num_bits(n: &Integer) -> usize { + if *n == 0i32 { + return 1; + } + n.significant_bits() as usize +} + +/// Extract a 64-bit signed integer and exponent approximation of n, +/// mirroring the C `mpz_get_si_2exp` used in the Reducer. +/// Returns (mantissa, exponent) where n ≈ mantissa * 2^(exponent - 63). +pub fn get_si_2exp(n: &Integer) -> (i64, i64) { + if *n == 0i32 { + return (0, 0); + } + let bits = num_bits(n) as i64; + let shift = if bits > 64 { (bits - 64) as u64 } else { 0 }; + let shifted = n.unsigned_abs_ref() >> shift; + let top_limb = shifted.as_limbs_asc().first().copied().unwrap_or(0); + let lg2 = 64 - top_limb.leading_zeros() as i64; + let mantissa_shift = 63 - lg2; + let mantissa = if mantissa_shift >= 0 { + (top_limb << mantissa_shift) as i64 + } else { + (top_limb >> (-mantissa_shift)) as i64 + }; + let mantissa = if *n < 0i32 { -mantissa } else { mantissa }; + (mantissa, bits) +} + +/// Extract the low word of (|x| >> shift_bits), where x is non-negative. +pub fn extract_uword_from_shift_nonneg(x: &Integer, shift_bits: i64) -> i64 { + let nat = x.unsigned_abs_ref(); + if shift_bits <= 0 { + return nat.as_limbs_asc().first().copied().unwrap_or(0) as i64; + } + let shifted = nat >> shift_bits as u64; + shifted.as_limbs_asc().first().copied().unwrap_or(0) as i64 +} + +/// Get bit length of the absolute value (matching chiavdf_mpz_bitlen_nonneg). +pub fn bitlen_nonneg(x: &Integer) -> i64 { + if *x == 0i32 { + return 1; + } + x.significant_bits() as i64 +} + +/// Trailing zeros in the absolute value (number of factors of 2). +pub fn trailing_zeros(n: &Integer) -> u64 { + n.trailing_zeros().unwrap_or(0) +} + +/// Lehmer-accelerated full extended GCD via malachite's built-in implementation. +/// Returns (gcd, x, y) such that gcd = x * a + y * b. +pub fn fast_extended_gcd(a: &Integer, b: &Integer) -> (Integer, Integer, Integer) { + let (gcd, x, y) = a.clone().extended_gcd(b.clone()); + (Integer::from(gcd), x, y) +} + +/// Half extended GCD: returns (gcd, y) where gcd ≡ y * b (mod a). +/// Only tracks the coefficient of b. +pub fn fast_gcd_coeff_b(a: &Integer, b: &Integer) -> (Integer, Integer) { + let (gcd, _x, y) = a.clone().extended_gcd(b.clone()); + (Integer::from(gcd), y) +} + +/// Compute gcd of two non-negative integers. +pub fn gcd_nonneg(a: &Integer, b: &Integer) -> Integer { + Integer::from( + a.unsigned_abs_ref() + .clone() + .gcd(b.unsigned_abs_ref().clone()), + ) +} + +/// Extract the low 64 bits of (n >> shift) treating n as non-negative. +/// Used in the Lehmer inner loop. +pub fn extract_word_unsigned(n: &Integer, shift: usize) -> i64 { + let nat = n.unsigned_abs_ref(); + if shift == 0 { + return nat.as_limbs_asc().first().copied().unwrap_or(0) as i64; + } + let shifted = nat >> shift as u64; + shifted.as_limbs_asc().first().copied().unwrap_or(0) as i64 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fast_extended_gcd() { + let cases: Vec<(i64, i64, i64)> = vec![ + (5, 3, 1), + (6, 4, 2), + (100, 37, 1), + (15, 10, 5), + (7, 5, 1), + (35, 20, 5), + (1000000, 999983, 1), + (0, 7, 7), + (7, 0, 7), + (1, 1, 1), + ]; + for (a, b, expected_gcd) in cases { + let ba = Integer::from(a); + let bb = Integer::from(b); + let (gcd, x, y) = fast_extended_gcd(&ba, &bb); + assert_eq!( + gcd, + Integer::from(expected_gcd), + "gcd({},{}) wrong: got {}", + a, + b, + gcd + ); + assert_eq!( + &x * &ba + &y * &bb, + gcd, + "Bezout identity failed for ({},{}): {}*{}+{}*{}≠{}", + a, + b, + x, + a, + y, + b, + gcd + ); + } + let (gcd, x, y) = fast_extended_gcd(&Integer::from(-15i64), &Integer::from(10i64)); + assert_eq!(gcd, Integer::from(5)); + assert_eq!(&x * Integer::from(-15i64) + &y * Integer::from(10i64), gcd); + } + + #[test] + fn test_jacobi() { + assert_eq!(jacobi(&Integer::from(2), &Integer::from(7)), 1); + assert_eq!(jacobi(&Integer::from(3), &Integer::from(7)), -1); + assert_eq!(jacobi(&Integer::from(5), &Integer::from(9)), 1); + assert_eq!(jacobi(&Integer::from(0), &Integer::from(7)), 0); + assert_eq!(jacobi(&Integer::from(1), &Integer::from(7)), 1); + } + + #[test] + fn test_fdiv() { + let a = Integer::from(-7i64); + let b = Integer::from(2i64); + assert_eq!(fdiv_q(&a, &b), Integer::from(-4i64)); + assert_eq!(fdiv_r(&a, &b), Integer::from(1i64)); + } + + #[test] + fn test_isqrt() { + assert_eq!(isqrt(&Integer::from(16)), Integer::from(4)); + assert_eq!(isqrt(&Integer::from(15)), Integer::from(3)); + assert_eq!(isqrt(&Integer::from(0)), Integer::from(0)); + } +} diff --git a/crates/chia-vdf-verify/src/lib.rs b/crates/chia-vdf-verify/src/lib.rs new file mode 100644 index 000000000..72f276186 --- /dev/null +++ b/crates/chia-vdf-verify/src/lib.rs @@ -0,0 +1,13 @@ +pub mod bqfc; +pub mod discriminant; +pub mod form; +pub mod integer; +pub mod nucomp; +pub mod primetest; +pub mod proof_common; +pub mod reducer; +pub mod verifier; +pub mod xgcd_partial; + +#[cfg(feature = "python")] +pub mod python; diff --git a/crates/chia-vdf-verify/src/nucomp.rs b/crates/chia-vdf-verify/src/nucomp.rs new file mode 100644 index 000000000..b423fe545 --- /dev/null +++ b/crates/chia-vdf-verify/src/nucomp.rs @@ -0,0 +1,249 @@ +//! NUCOMP and NUDUPL form composition. +//! +//! Port of chiavdf/src/nucomp.h (William Hart's algorithm). + +use crate::form::Form; +use crate::integer::{divexact, fast_extended_gcd, fast_gcd_coeff_b, fdiv_q, fdiv_r, tdiv_r}; +use crate::xgcd_partial::xgcd_partial; +use malachite_base::num::basic::traits::Zero; +use malachite_nz::integer::Integer; + +/// Compose two forms: result = f * g. +/// This is qfb_nucomp. +pub fn nucomp(f: &Form, g: &Form, d: &Integer, l: &Integer) -> Form { + if f.a > g.a { + return nucomp(g, f, d, l); + } + + let a1 = f.a.clone(); + let a2 = g.a.clone(); + let c2 = g.c.clone(); + + let ss = (&f.b + &g.b) >> 1u64; + let m = (&f.b - &g.b) >> 1u64; + + let t = fdiv_r(&a2, &a1); + let (sp, v1) = if t == 0i32 { + (a1.clone(), Integer::ZERO) + } else { + let (gcd, x, _) = fast_extended_gcd(&t, &a1); + (gcd, x) + }; + + let mut k = fdiv_r(&(&m * &v1), &a1); + + let (a1_new, a2_new, c2_new); + + if sp != 1i32 { + let (s, v2, u2) = gcd_ext3(&ss, &sp); + + k = &k * &u2 - &v2 * &c2; + + if s != 1i32 { + a1_new = divexact(&a1, &s); + a2_new = divexact(&a2, &s); + c2_new = &c2 * &s; + } else { + a1_new = a1.clone(); + a2_new = a2.clone(); + c2_new = c2.clone(); + } + + k = fdiv_r(&k, &a1_new); + } else { + a1_new = a1.clone(); + a2_new = a2.clone(); + c2_new = c2.clone(); + } + + if a1_new < *l { + let t = &a2_new * &k; + let ca = &a2_new * &a1_new; + let cb = (&t << 1u64) + &g.b; + let cc_num = (&g.b + &t) * &k + &c2_new; + let cc = divexact(&cc_num, &a1_new); + + Form::new(ca, cb, cc) + } else { + let mut r2 = a1_new.clone(); + let mut r1 = k; + let mut co2 = Integer::ZERO; + let mut co1 = Integer::ZERO; + + xgcd_partial(&mut co2, &mut co1, &mut r2, &mut r1, l); + + let m1 = divexact(&(&m * &co1 + &a2_new * &r1), &a1_new); + + let m2 = divexact(&(&ss * &r1 - &c2_new * &co1), &a1_new); + + let ca_unsigned = &r1 * &m1 - &co1 * &m2; + let mut ca = if co1 < 0i32 { + ca_unsigned + } else { + -ca_unsigned + }; + + let t_val = &a2_new * &r1; + + let cb_inner = &t_val - &ca * &co2; + let cb_scaled = &cb_inner << 1u64; + let cb_divided = divexact(&cb_scaled, &co1); + let cb_shifted = cb_divided - &g.b; + let ca2 = &ca << 1u64; + let cb = fdiv_r(&cb_shifted, &ca2); + + let cc_num = &cb * &cb - d; + let cc_denom = &ca << 2u64; + let mut cc = divexact(&cc_num, &cc_denom); + + if ca < 0i32 { + ca = -ca; + cc = -cc; + } + + Form::new(ca, cb, cc) + } +} + +/// Extended GCD returning (gcd, coeff_a, coeff_b) where gcd = coeff_a * a + coeff_b * b. +fn gcd_ext3(a: &Integer, b: &Integer) -> (Integer, Integer, Integer) { + fast_extended_gcd(a, b) +} + +/// Duplicate a form: result = f^2. +/// This is qfb_nudupl. +pub fn nudupl(f: &Form, d: &Integer, l: &Integer) -> Form { + let a1 = f.a.clone(); + let c1 = f.c.clone(); + + let b_abs = if f.b < 0i32 { + -f.b.clone() + } else { + f.b.clone() + }; + let (s, v2) = { + let (gcd, coeff_b) = fast_gcd_coeff_b(&a1, &b_abs); + let v2 = if f.b < 0i32 { -coeff_b } else { coeff_b }; + (gcd, v2) + }; + + let k_raw = -&c1 * &v2; + let mut k = tdiv_r(&k_raw, &a1); + if k < 0i32 { + k += &a1; + } + + let a1_new; + let c1_new; + + let s_is_1 = s == 1i32; + if !s_is_1 { + a1_new = fdiv_q(&a1, &s); + c1_new = &c1 * &s; + } else { + a1_new = a1.clone(); + c1_new = c1.clone(); + } + + if a1_new < *l { + let t = &a1_new * &k; + let new_a = &a1_new * &a1_new; + let cb = (&t << 1u64) + &f.b; + let cc_num = (&f.b + &t) * &k + &c1_new; + let cc = fdiv_q(&cc_num, &a1_new); + + Form::new(new_a, cb, cc) + } else { + let mut r2 = a1_new.clone(); + let mut r1 = k; + let mut co2 = Integer::ZERO; + let mut co1 = Integer::ZERO; + + xgcd_partial(&mut co2, &mut co1, &mut r2, &mut r1, l); + + let m2_num = &f.b * &r1 - &c1_new * &co1; + let m2 = divexact(&m2_num, &a1_new); + + let mut new_a = &r1 * &r1 - &co1 * &m2; + if co1 >= 0i32 { + new_a = -new_a; + } + + let cb_tmp = &new_a * &co2 - &a1_new * &r1; + let cb_neg = -cb_tmp; + let cb_doubled = &cb_neg << 1u64; + let cb_div = divexact(&cb_doubled, &co1); + let cb_pre = cb_div - &f.b; + let two_new_a = &new_a << 1u64; + let cb = fdiv_r(&cb_pre, &two_new_a); + + let cc_num = &cb * &cb - d; + let cc_pre = divexact(&cc_num, &new_a); + let cc = &cc_pre >> 2u64; + + let (final_a, final_c) = if new_a < 0i32 { + (-new_a, -cc) + } else { + (new_a, cc) + }; + + Form::new(final_a, cb, final_c) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::form::Form; + + fn discriminant_ok(f: &Form, d: &Integer) -> bool { + let disc = &f.b * &f.b - Integer::from(4i32) * &f.a * &f.c; + &disc == d + } + + #[test] + fn test_nucomp_preserves_discriminant() { + let d = Integer::from(-47i64); + let l = Form::compute_l(&d); + let f = Form::new( + Integer::from(2i32), + Integer::from(1i32), + Integer::from(6i32), + ); + let g = Form::new( + Integer::from(3i32), + Integer::from(1i32), + Integer::from(4i32), + ); + assert!(discriminant_ok(&f, &d)); + assert!(discriminant_ok(&g, &d)); + let result = nucomp(&f, &g, &d, &l); + assert!( + discriminant_ok(&result, &d), + "nucomp result has wrong discriminant: a={}, b={}, c={}", + result.a, + result.b, + result.c + ); + } + + #[test] + fn test_nudupl_preserves_discriminant() { + let d = Integer::from(-47i64); + let l = Form::compute_l(&d); + let f = Form::new( + Integer::from(2i32), + Integer::from(1i32), + Integer::from(6i32), + ); + assert!(discriminant_ok(&f, &d)); + let result = nudupl(&f, &d, &l); + assert!( + discriminant_ok(&result, &d), + "nudupl result has wrong discriminant: a={}, b={}, c={}", + result.a, + result.b, + result.c + ); + } +} diff --git a/crates/chia-vdf-verify/src/primetest.rs b/crates/chia-vdf-verify/src/primetest.rs new file mode 100644 index 000000000..0e24b45f2 --- /dev/null +++ b/crates/chia-vdf-verify/src/primetest.rs @@ -0,0 +1,239 @@ +//! BPSW primality test and HashPrime. +//! +//! Ports: +//! - chiavdf/src/primetest.h (is_prime_bpsw) +//! - chiavdf/src/proof_common.h (HashPrime) + +use crate::integer::{from_bytes_be, jacobi, modpow}; +use malachite_base::num::arithmetic::traits::{DivMod, Parity}; +use malachite_base::num::basic::traits::One; +use malachite_base::num::logic::traits::{BitAccess, SignificantBits}; +use malachite_nz::integer::Integer; +use sha2::{Digest, Sha256}; + +/// Small prime products for trial division (subset — enough for fast rejection). +static SMALL_PRIMES: &[u64] = &[ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, + 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, + 197, 199, +]; + +/// Miller-Rabin test with given base `b` modulo `n`. +/// Returns true if n is probably prime (passes MR with this base). +pub fn miller_rabin(n: &Integer, base: &Integer) -> bool { + let n_minus1 = n - Integer::ONE; + let s = n_minus1.trailing_zeros().unwrap_or(0) as usize; + let d = &n_minus1 >> s as u64; + + let mut b = modpow(base, &d, n); + + if b == 1i32 { + return true; + } + + for _ in 0..s { + let b_plus1 = &b + Integer::ONE; + if b_plus1 == *n { + return true; + } + b = modpow(&b, &Integer::from(2u32), n); + } + false +} + +/// Find parameters (p, q) for Lucas test such that Jacobi(D, n) = -1. +fn find_pq(n: &Integer) -> Option<(i64, i64)> { + let mut d = 5i64; + for _ in 0..500 { + let d_sign = if d % 4 == 1 { d } else { -d }; + let d_big = Integer::from(d_sign); + if jacobi(&d_big, n) == -1 { + if d_sign == 5 { + return Some((5, 5)); + } else { + return Some((1, (1 - d_sign) / 4)); + } + } + d = d.abs() + 2; + } + None +} + +/// Lucas-V probable prime test (vprp). +fn is_vprp(n: &Integer) -> bool { + let (p, q) = match find_pq(n) { + Some(pq) => pq, + None => return false, + }; + + let e = n + Integer::ONE; + let v1 = find_lucas_v(&e, n, p, q); + + let two_q = Integer::from(2 * q); + let v1_mod = v1.div_mod(n).1; + let two_q_mod = two_q.div_mod(n).1; + + v1_mod == two_q_mod +} + +/// Compute Lucas-V sequence value V_{n+1} mod m using the doubling method. +fn find_lucas_v(e: &Integer, m: &Integer, p: i64, q: i64) -> Integer { + let l = e.significant_bits() as usize; + + let mut u1 = Integer::ONE; + let mut u2 = Integer::from(p); + let minus_2q = -2 * q; + + for i in (0..l.saturating_sub(1)).rev() { + let tmp2 = &u2 * &u1; + let u2_sq = &u2 * &u2; + let u1_sq = &u1 * &u1; + + if e.get_bit(i as u64) { + u1 = &u2_sq - Integer::from(q) * &u1_sq; + u2 = if p != 1 { + Integer::from(p) * &u2_sq + Integer::from(minus_2q) * &tmp2 + } else { + &u2_sq + Integer::from(minus_2q) * &tmp2 + }; + } else { + u2 = &u2_sq - Integer::from(q) * &u1_sq; + let tmp3 = Integer::from(2) * &tmp2; + u1 = if p != 1 { + tmp3 - Integer::from(p) * &u1_sq + } else { + tmp3 - &u1_sq + }; + } + + u1 = u1.div_mod(m).1; + u2 = u2.div_mod(m).1; + } + + Integer::from(2) * &u2 - Integer::from(p) * &u1 +} + +/// BPSW primality test. +/// Returns true if n is (very likely) prime. +pub fn is_prime_bpsw(n: &Integer) -> bool { + if *n <= 1i32 { + return false; + } + if *n == 2i32 || *n == 3i32 { + return true; + } + if n.even() { + return false; + } + + for &p in SMALL_PRIMES { + let p_big = Integer::from(p); + if *n == p_big { + return true; + } + if (n % &p_big) == 0i32 { + return false; + } + } + + let base2 = Integer::from(2u32); + if !miller_rabin(n, &base2) { + return false; + } + + is_vprp(n) +} + +/// HashPrime: generate a pseudoprime of given bit length from seed. +/// +/// Uses iterative SHA256 to expand seed, then applies bitmask and tests primality. +/// Matches chiavdf's HashPrime(seed, length, bitmask). +pub fn hash_prime(seed: &[u8], length: usize, bitmask: &[usize]) -> Integer { + assert!(length.is_multiple_of(8), "length must be multiple of 8"); + let byte_len = length / 8; + + let mut sprout = seed.to_vec(); + + loop { + let mut blob = Vec::with_capacity(byte_len); + + while blob.len() * 8 < length { + for i in (0..sprout.len()).rev() { + sprout[i] = sprout[i].wrapping_add(1); + if sprout[i] != 0 { + break; + } + } + + let hash = Sha256::digest(&sprout); + let remaining = byte_len - blob.len(); + let take = remaining.min(hash.len()); + blob.extend_from_slice(&hash[..take]); + } + + assert_eq!(blob.len(), byte_len); + + let mut p = from_bytes_be(&blob); + + for &bit in bitmask { + p |= Integer::ONE << bit as u64; + } + + p |= Integer::ONE; + + if is_prime_bpsw(&p) { + return p; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_miller_rabin_known_primes() { + let base2 = Integer::from(2u32); + for &p in &[3u64, 5, 7, 11, 13, 17, 19, 23, 997, 7919] { + let n = Integer::from(p); + assert!(miller_rabin(&n, &base2), "{} should pass MR(2)", p); + } + } + + #[test] + fn test_miller_rabin_composites() { + let base2 = Integer::from(2u32); + let _results: Vec<_> = [9u64, 15, 21, 25, 35, 49, 77, 91] + .iter() + .map(|&c| miller_rabin(&Integer::from(c), &base2)) + .collect(); + let n9 = Integer::from(9u64); + assert!(!miller_rabin(&n9, &base2), "9 should fail MR(2)"); + } + + #[test] + fn test_is_prime_bpsw() { + for &p in &[2u64, 3, 5, 7, 11, 13, 997, 7919, 104729] { + assert!(is_prime_bpsw(&Integer::from(p)), "{} should be prime", p); + } + for &c in &[4u64, 6, 9, 15, 25, 35, 49, 100] { + assert!( + !is_prime_bpsw(&Integer::from(c)), + "{} should be composite", + c + ); + } + } + + #[test] + fn test_hash_prime_is_prime() { + let seed = b"test_seed_12345"; + let p = hash_prime(seed, 256, &[255]); + assert!(is_prime_bpsw(&p), "hash_prime result should be prime"); + assert_eq!( + p.significant_bits(), + 256, + "hash_prime result should have correct bit length" + ); + } +} diff --git a/crates/chia-vdf-verify/src/proof_common.rs b/crates/chia-vdf-verify/src/proof_common.rs new file mode 100644 index 000000000..1e7aba8aa --- /dev/null +++ b/crates/chia-vdf-verify/src/proof_common.rs @@ -0,0 +1,126 @@ +//! FastPow, GetB, SerializeForm, DeserializeForm, FastPowFormNucomp. +//! +//! Port of chiavdf/src/proof_common.h. + +use crate::bqfc::{deserialize, serialize}; +use crate::form::Form; +use crate::integer::{modpow, num_bits}; +use crate::nucomp::{nucomp, nudupl}; +use crate::primetest::hash_prime; +use crate::reducer::reduce; +use malachite_base::num::logic::traits::{BitAccess, SignificantBits}; +use malachite_nz::integer::Integer; + +/// B_bits = 264 +pub const B_BITS: usize = 264; +/// B_bytes = 33 +pub const B_BYTES: usize = B_BITS.div_ceil(8); + +/// Compute 2^b mod c. +/// This is FastPow(2, b, c) in C. +pub fn fast_pow(b: u64, c: &Integer) -> Integer { + if *c == 0i32 { + panic!("FastPow: division by zero"); + } + let base = Integer::from(2u32); + let exp = Integer::from(b); + modpow(&base, &exp, c) +} + +/// Serialize form to BQFC_FORM_SIZE bytes. +/// Reduces the form first. +pub fn serialize_form(f: &mut Form, d_bits: usize) -> Vec { + f.reduce(); + serialize(&f.a, &f.b, d_bits) +} + +/// Deserialize form from BQFC_FORM_SIZE bytes with discriminant D. +pub fn deserialize_form(d: &Integer, bytes: &[u8]) -> Result { + let (a, b) = deserialize(d, bytes)?; + if a == 0i32 { + return Err("deserialized form has a=0".to_string()); + } + let f = Form::from_abd(a, b, d); + if !f.is_reduced() { + return Err("deserialized form is not reduced".to_string()); + } + Ok(f) +} + +/// Compute GetB(D, x, y): +/// B = HashPrime(serialize(x) || serialize(y), 264, {263}) +pub fn get_b(d: &Integer, x: &mut Form, y: &mut Form) -> Integer { + let d_bits = num_bits(d); + let sx = serialize_form(x, d_bits); + let sy = serialize_form(y, d_bits); + let mut seed = sx; + seed.extend_from_slice(&sy); + hash_prime(&seed, B_BITS, &[B_BITS - 1]) +} + +/// Exponentiate a form by num_iterations using binary method with nucomp/nudupl. +/// This is FastPowFormNucomp. +pub fn fast_pow_form_nucomp(x: &Form, d: &Integer, num_iterations: &Integer, l: &Integer) -> Form { + if *num_iterations == 0i32 { + return Form::identity(d); + } + + let mut res = x.clone(); + let n_bits = num_iterations.significant_bits() as usize; + + let do_lazy_reduce = { + let d_limbs = num_bits(d).div_ceil(64); + d_limbs / 2 + }; + + for i in (0..n_bits.saturating_sub(1)).rev() { + res = nudupl(&res, d, l); + + let a_limbs = num_bits(&res.a).div_ceil(64); + if a_limbs > do_lazy_reduce { + reduce(&mut res); + } + + if num_iterations.get_bit(i as u64) { + res = nucomp(&res, x, d, l); + } + } + + reduce(&mut res); + res +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::discriminant::create_discriminant; + + #[test] + fn test_fast_pow() { + let modulus = Integer::from(1000003u64); + assert_eq!(fast_pow(10, &modulus), Integer::from(1024u64)); + } + + #[test] + fn test_identity_exponentiation() { + let seed = b"test_disc"; + let d = create_discriminant(seed, 512); + let l = Form::compute_l(&d); + let x = Form::identity(&d); + let result = fast_pow_form_nucomp(&x, &d, &Integer::from(100u32), &l); + assert_eq!(result.a, Integer::from(1u32)); + assert_eq!(result.b, Integer::from(1u32)); + } + + #[test] + fn test_serialize_deserialize_identity() { + let seed = b"test_disc"; + let d = create_discriminant(seed, 512); + let d_bits = num_bits(&d); + let mut f = Form::identity(&d); + let bytes = serialize_form(&mut f, d_bits); + let f2 = deserialize_form(&d, &bytes).unwrap(); + assert_eq!(f.a, f2.a); + assert_eq!(f.b, f2.b); + } +} diff --git a/crates/chia-vdf-verify/src/python.rs b/crates/chia-vdf-verify/src/python.rs new file mode 100644 index 000000000..d407b6e58 --- /dev/null +++ b/crates/chia-vdf-verify/src/python.rs @@ -0,0 +1,50 @@ +use pyo3::prelude::*; + +/// Create a discriminant from a seed and bit length. +/// Returns a hex string representation of the (negative) discriminant, +/// matching the chiavdf format: e.g. "-3abc...". +/// Callers convert via: int(result, 16) +#[pyfunction] +fn create_discriminant(seed: &[u8], length: usize) -> String { + let d = crate::discriminant::create_discriminant(seed, length); + // d is negative; format magnitude as hex with leading '-' + format!("-{:x}", d.magnitude()) +} + +/// Verify a VDF N-Wesolowski proof. +/// +/// Arguments match the chiavdf.verify_n_wesolowski signature: +/// disc - discriminant as a decimal string (negative) +/// input_el - 100-byte serialized input form +/// output - serialized output form + proof bytes concatenated +/// number_of_iterations - total VDF iterations +/// discriminant_size - discriminant bit size (API compat only, unused) +/// witness_type - proof depth (0, 1, 2, …) +#[pyfunction] +fn verify_n_wesolowski( + disc: &str, + input_el: &[u8], + output: &[u8], + number_of_iterations: u64, + _discriminant_size: usize, + witness_type: u64, +) -> bool { + let d: num_bigint::BigInt = match disc.parse() { + Ok(v) => v, + Err(_) => return false, + }; + crate::verifier::check_proof_of_time_n_wesolowski( + &d, + input_el, + output, + number_of_iterations, + witness_type, + ) +} + +#[pymodule] +fn chia_vdf_verify(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(create_discriminant, m)?)?; + m.add_function(wrap_pyfunction!(verify_n_wesolowski, m)?)?; + Ok(()) +} diff --git a/crates/chia-vdf-verify/src/reducer.rs b/crates/chia-vdf-verify/src/reducer.rs new file mode 100644 index 000000000..8f1c54b0e --- /dev/null +++ b/crates/chia-vdf-verify/src/reducer.rs @@ -0,0 +1,206 @@ +//! Pulmark form reducer. +//! +//! Port of chiavdf/src/Reducer.h. +//! Reduces a quadratic form (a, b, c) to its canonical reduced representative. + +use crate::form::Form; +use crate::integer::get_si_2exp; +use malachite_base::num::arithmetic::traits::CeilingDivMod; +use malachite_nz::integer::Integer; +use malachite_nz::natural::Natural; + +const THRESH: i64 = 1i64 << 31; +const EXP_THRESH: i64 = 31; + +/// Reduce form f in place. +pub fn reduce(f: &mut Form) { + while !is_reduced(f) { + let (a_val, a_exp) = get_si_2exp(&f.a); + let (b_val, b_exp) = get_si_2exp(&f.b); + let (c_val, c_exp) = get_si_2exp(&f.c); + + let max_exp = *[a_exp, b_exp, c_exp].iter().max().unwrap() + 1; + let min_exp = *[a_exp, b_exp, c_exp].iter().min().unwrap(); + + if max_exp - min_exp > EXP_THRESH { + reducer_simple(f); + continue; + } + + let a_sh = max_exp - a_exp; + let b_sh = max_exp - b_exp; + let c_sh = max_exp - c_exp; + + let a = a_val >> a_sh; + let b = b_val >> b_sh; + let c = c_val >> c_sh; + + let (u, v, w, x) = calc_uvwx(a, b, c); + + let new_a = f.a.clone() * Integer::from(u * u) + + f.b.clone() * Integer::from(u * w) + + f.c.clone() * Integer::from(w * w); + let new_b = f.a.clone() * Integer::from(2 * u * v) + + f.b.clone() * Integer::from(u * x + v * w) + + f.c.clone() * Integer::from(2 * w * x); + let new_c = f.a.clone() * Integer::from(v * v) + + f.b.clone() * Integer::from(v * x) + + f.c.clone() * Integer::from(x * x); + + f.a = new_a; + f.b = new_b; + f.c = new_c; + } +} + +/// Simple reducer step (used when exponent spread is large). +fn reducer_simple(f: &mut Form) { + let r = ceildiv(&f.b, &f.c); + let r_plus1 = r + Integer::from(1i32); + let s = r_plus1 >> 1u64; + + let cs = &f.c * &s; + let cs2 = &cs << 1u64; + + let m = &cs - &f.b; + + let new_b = &cs2 - &f.b; + + let old_a = f.a.clone(); + + f.a = f.c.clone(); + + f.c = old_a + &s * &m; + f.b = new_b; +} + +/// Ceiling division: ceil(a/b). +fn ceildiv(a: &Integer, b: &Integer) -> Integer { + if *b > 0i32 { + a.ceiling_div_mod(b).0 + } else { + a / b + } +} + +/// Check if the form is reduced and normalize if needed. +/// Returns true if already reduced (but may have swapped a/c or negated b). +fn is_reduced(f: &mut Form) -> bool { + let abs_a: Natural = f.a.unsigned_abs_ref().clone(); + let abs_b: Natural = f.b.unsigned_abs_ref().clone(); + let abs_c: Natural = f.c.unsigned_abs_ref().clone(); + + if abs_a < abs_b || abs_c < abs_b { + return false; + } + + let a_cmp_c = abs_a.cmp(&abs_c); + if a_cmp_c == std::cmp::Ordering::Greater { + std::mem::swap(&mut f.a, &mut f.c); + f.b = -f.b.clone(); + } else if a_cmp_c == std::cmp::Ordering::Equal && f.b < 0i32 { + f.b = -f.b.clone(); + } + true +} + +/// Lehmer acceleration step: compute (u, v, w, x) 2x2 matrix. +fn calc_uvwx(mut a: i64, mut b: i64, mut c: i64) -> (i64, i64, i64, i64) { + let mut u_ = 1i64; + let mut v_ = 0i64; + let mut w_ = 0i64; + let mut x_ = 1i64; + + let mut u; + let mut v; + let mut w; + let mut x; + + loop { + u = u_; + v = v_; + w = w_; + x = x_; + + if c == 0 { + break; + } + + let s = if b >= 0 { + (b + c) / (c << 1) + } else { + -(-b + c) / (c << 1) + }; + + let a_ = a; + let b_ = b; + + a = c; + b = -b + (c.wrapping_mul(s) << 1); + c = a_ - s * (b_ - c.wrapping_mul(s)); + + u_ = v; + v_ = -u + s.wrapping_mul(v); + w_ = x; + x_ = -w + s.wrapping_mul(x); + + let below_threshold = (v_.abs() | x_.abs()) <= THRESH; + if !(below_threshold && a > c && c > 0) { + if below_threshold { + u = u_; + v = v_; + w = w_; + x = x_; + } + break; + } + } + + (u, v, w, x) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::form::Form; + + fn disc_check(f: &Form, d: &Integer) -> bool { + let disc = &f.b * &f.b - Integer::from(4i32) * &f.a * &f.c; + &disc == d + } + + #[test] + fn test_reduce_preserves_discriminant() { + let d = Integer::from(-47i64); + let mut f = Form::new( + Integer::from(3i32), + Integer::from(1i32), + Integer::from(4i32), + ); + assert!(disc_check(&f, &d)); + reduce(&mut f); + assert!(disc_check(&f, &d), "discriminant changed after reduction"); + assert!( + f.is_reduced(), + "form not reduced: a={}, b={}, c={}", + f.a, + f.b, + f.c + ); + } + + #[test] + fn test_reduce_idempotent() { + let d = Integer::from(-47i64); + let mut f = Form::new( + Integer::from(2i32), + Integer::from(1i32), + Integer::from(6i32), + ); + assert!(disc_check(&f, &d)); + reduce(&mut f); + let f2 = f.clone(); + reduce(&mut f); + assert_eq!(f, f2, "reduction should be idempotent"); + } +} diff --git a/crates/chia-vdf-verify/src/verifier.rs b/crates/chia-vdf-verify/src/verifier.rs new file mode 100644 index 000000000..17ebb023f --- /dev/null +++ b/crates/chia-vdf-verify/src/verifier.rs @@ -0,0 +1,152 @@ +//! VDF proof verification. +//! +//! Port of chiavdf/src/verifier.h. + +use crate::bqfc::BQFC_FORM_SIZE; +use crate::form::Form; +use crate::nucomp::nucomp; +use crate::proof_common::{deserialize_form, fast_pow, fast_pow_form_nucomp, get_b, B_BYTES}; +use crate::reducer::reduce; +use malachite_nz::integer::Integer; + +/// Verify a single Wesolowski segment. +/// +/// Checks: proof^B * x^r == y +/// where r = 2^iters mod B. +/// +/// Returns Ok(y_computed) on success, Err on failure. +pub fn verify_weso_segment( + d: &Integer, + x: &Form, + proof: &Form, + b: &Integer, + iters: u64, +) -> Result { + let l = Form::compute_l(d); + let r = fast_pow(iters, b); + + let f1 = fast_pow_form_nucomp(proof, d, b, &l); + let f2 = fast_pow_form_nucomp(x, d, &r, &l); + + let mut out_y = nucomp(&f1, &f2, d, &l); + reduce(&mut out_y); + + Ok(out_y) +} + +/// Verify a Wesolowski proof. +/// +/// Checks: proof^B * x^r == y +pub fn verify_wesolowski_proof(d: &Integer, x: &Form, y: &Form, proof: &Form, iters: u64) -> bool { + let l = Form::compute_l(d); + let mut x_mut = x.clone(); + let mut y_mut = y.clone(); + let b = get_b(d, &mut x_mut, &mut y_mut); + let r = fast_pow(iters, &b); + + let f1 = fast_pow_form_nucomp(proof, d, &b, &l); + let f2 = fast_pow_form_nucomp(x, d, &r, &l); + + let mut result = nucomp(&f1, &f2, d, &l); + reduce(&mut result); + + result == *y +} + +/// Verify a N-Wesolowski proof blob. +/// +/// proof_blob format: +/// [y_form (100 bytes)] [proof_form (100 bytes)] [segments from back...] +/// Each segment: [iters (8 bytes)] [B (33 bytes)] [proof_form (100 bytes)] +/// +/// Returns true if proof is valid. +pub fn check_proof_of_time_n_wesolowski( + d: &Integer, + x_s: &[u8], + proof_blob: &[u8], + iterations: u64, + depth: u64, +) -> bool { + let form_size = BQFC_FORM_SIZE; + let segment_len = 8 + B_BYTES + form_size; + let base_len = 2 * form_size; + + if depth > (usize::MAX - base_len) as u64 / segment_len as u64 { + return false; + } + + let expected_len = base_len + depth as usize * segment_len; + if proof_blob.len() != expected_len { + return false; + } + + let mut x = match deserialize_form(d, x_s) { + Ok(f) => f, + Err(_) => return false, + }; + + let mut remaining_iters = iterations; + let mut i = proof_blob.len(); + + while i > base_len { + i -= segment_len; + + let segment_iters = u64::from_be_bytes(proof_blob[i..i + 8].try_into().unwrap()); + + let b_bytes = &proof_blob[i + 8..i + 8 + B_BYTES]; + let b = crate::integer::from_bytes_be(b_bytes); + + let proof_bytes = &proof_blob[i + 8 + B_BYTES..i + 8 + B_BYTES + form_size]; + let proof = match deserialize_form(d, proof_bytes) { + Ok(f) => f, + Err(_) => return false, + }; + + let out_y = match verify_weso_segment(d, &x, &proof, &b, segment_iters) { + Ok(y) => y, + Err(_) => return false, + }; + + let mut x_clone = x.clone(); + let mut out_y_clone = out_y.clone(); + let computed_b = get_b(d, &mut x_clone, &mut out_y_clone); + if computed_b != b { + return false; + } + + x = out_y; + + if segment_iters > remaining_iters { + return false; + } + remaining_iters -= segment_iters; + } + + let y = match deserialize_form(d, &proof_blob[..form_size]) { + Ok(f) => f, + Err(_) => return false, + }; + let proof = match deserialize_form(d, &proof_blob[form_size..2 * form_size]) { + Ok(f) => f, + Err(_) => return false, + }; + + verify_wesolowski_proof(d, &x, &y, &proof, remaining_iters) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::discriminant::create_discriminant; + + #[test] + fn test_verify_basic_structure() { + let seed = b"test"; + let d = create_discriminant(seed, 512); + let x = Form::identity(&d); + + let iters = 100u64; + let result = verify_wesolowski_proof(&d, &x, &x, &x, iters); + let _ = result; + } +} diff --git a/crates/chia-vdf-verify/src/xgcd_partial.rs b/crates/chia-vdf-verify/src/xgcd_partial.rs new file mode 100644 index 000000000..5cbc2a635 --- /dev/null +++ b/crates/chia-vdf-verify/src/xgcd_partial.rs @@ -0,0 +1,136 @@ +//! Partial extended GCD (Lehmer-accelerated). +//! +//! Port of `mpz_xgcd_partial` from chiavdf/src/xgcd_partial.c. +//! +//! On exit: co2*r1_orig - co1*r2_orig = ±r2_final +//! Terminates when r1 <= L. + +use crate::integer::{bitlen_nonneg, extract_uword_from_shift_nonneg, LIMB_BITS}; +use malachite_base::num::arithmetic::traits::DivRem; +use malachite_base::num::basic::traits::{NegativeOne, Zero}; +use malachite_nz::integer::Integer; + +/// Partial extended GCD. +/// Inputs: r2, r1, L (all non-negative, r2 >= r1). +/// On return: +/// - r1 <= L (termination condition) +/// - co2*r1_in - co1*r2_in = ±r2_out (approximately) +/// - r2_out >= 0 +pub fn xgcd_partial( + co2: &mut Integer, + co1: &mut Integer, + r2: &mut Integer, + r1: &mut Integer, + l: &Integer, +) { + *co2 = Integer::ZERO; + *co1 = Integer::NEGATIVE_ONE; + + while *r1 != 0i32 && &*r1 > l { + let bits2 = bitlen_nonneg(r2); + let bits1 = bitlen_nonneg(r1); + let bits = std::cmp::max(bits2, bits1) - LIMB_BITS as i64 + 1; + let bits = if bits < 0 { 0 } else { bits }; + + let mut rr2 = extract_uword_from_shift_nonneg(r2, bits); + let mut rr1 = extract_uword_from_shift_nonneg(r1, bits); + let bb = extract_uword_from_shift_nonneg(l, bits); + + let mut aa2: i64 = 0; + let mut aa1: i64 = 1; + let mut bb2: i64 = 1; + let mut bb1: i64 = 0; + + let mut i = 0usize; + loop { + if rr1 == 0 || rr1 <= bb { + break; + } + let qq = rr2 / rr1; + + let t1 = rr2 - qq * rr1; + let t2 = aa2 - qq * aa1; + let t3 = bb2 - qq * bb1; + + if i & 1 != 0 { + if t1 < -t3 || rr1 - t1 < t2 - aa1 { + break; + } + } else if t1 < -t2 || rr1 - t1 < t3 - bb1 { + break; + } + + rr2 = rr1; + rr1 = t1; + aa2 = aa1; + aa1 = t2; + bb2 = bb1; + bb1 = t3; + i += 1; + } + + if i == 0 { + let (q, rem) = (&*r2).div_rem(&*r1); + *r2 = std::mem::replace(r1, rem); + *co2 -= &q * &*co1; + std::mem::swap(co2, co1); + } else { + let r = r2.clone() * Integer::from(bb2) + r1.clone() * Integer::from(aa2); + let new_r1 = r1.clone() * Integer::from(aa1) + r2.clone() * Integer::from(bb1); + *r2 = r; + *r1 = new_r1; + + let new_co2 = co2.clone() * Integer::from(bb2) + co1.clone() * Integer::from(aa2); + let new_co1 = co1.clone() * Integer::from(aa1) + co2.clone() * Integer::from(bb1); + *co2 = new_co2; + *co1 = new_co1; + + if *r1 < 0i32 { + *co1 = -co1.clone(); + *r1 = -r1.clone(); + } + if *r2 < 0i32 { + *co2 = -co2.clone(); + *r2 = -r2.clone(); + } + } + } + + if *r2 < 0i32 { + *co2 = -co2.clone(); + *co1 = -co1.clone(); + *r2 = -r2.clone(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_xgcd_partial_basic() { + let mut co2 = Integer::ZERO; + let mut co1 = Integer::ZERO; + let mut r2 = Integer::from(100u32); + let mut r1 = Integer::from(37u32); + let l = Integer::from(10u32); + + xgcd_partial(&mut co2, &mut co1, &mut r2, &mut r1, &l); + + assert!(r1 <= l, "r1={} should be <= L={}", r1, l); + assert!(r2 >= 0i32, "r2 should be non-negative"); + } + + #[test] + fn test_xgcd_partial_identity() { + let mut co2 = Integer::ZERO; + let mut co1 = Integer::ZERO; + let mut r2 = Integer::from(100u32); + let mut r1 = Integer::from(5u32); + let l = Integer::from(10u32); + + xgcd_partial(&mut co2, &mut co1, &mut r2, &mut r1, &l); + + assert!(r1 <= l); + } +} diff --git a/crates/chia-vdf-verify/tests/fixtures/vdf.txt b/crates/chia-vdf-verify/tests/fixtures/vdf.txt new file mode 100644 index 000000000..f485a5569 --- /dev/null +++ b/crates/chia-vdf-verify/tests/fixtures/vdf.txt @@ -0,0 +1,660 @@ +9104c5b5e45d48f374efa0488fe6a617790e9aecb3c9cddec06809b09f45ce9b +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200553bf0f382fc65a94f20afad5dbce2c1ee8ba3bf93053559ac9960c8fd80ac2222e9b649701a4141a4d8999f0dbfe0c39ea744096598a7528328e5199f0aa30aec8aae8ab5018bf1245329a8272ddff1afbd87ad2eaba1b7fd57bd25edc62e0b010000003f0ffcd0dc307a2aa4678bafba661c77d176ef23afc86e7ea9f4f9eac52b8e1850748019245ecc96547da9b731dc72cded5582a9b0c63e13fd42446c7b28b41d3ded1d0b666d5ddb5b29719e4ebe70969e67e42ddd8591eae60d83dbe619f1250400 +129499136 +0 +2d28763748335ea43745f47ea4e9db3ebdd07de623fc06be3901b08b39c5feb2 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03007ab9d2dff2325f9afff2059b552486a99e9afbc61f2c59356832b11dcd761c7301a3617cb66861048773a7370bd26e444325abe44497a8a5e126bc4b004fca505f1a0e6aac1fdd4c6988c55d0d1bef53764cb20a76180f002edc4cf311d45573010001009e0b11c329cd15e34a0db5fa614a01fd8505c9a53b91792f6bff3732710ec12c4096d5f28a55276435428f412c1443443016c2568235faafe3935ed20cd8231733333b0111cc453c2b9812dfaccdb1af43c4e52f0ba62240a19b58b5e7056c3a0100 +130023424 +0 +dc234a8cd98daf561cbe97af5167aff8feb9ee271ae6cbf8d3b19c33a30364a3 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +010092b7b25f26954556f1bd7fa87c0ba48496ec55122f0d73b0569efc3227998575d89d11209a994c8effe5ad33b1bb40e02aeb0f9b6ef26a3c5c69f60b9cb9e03a835dfe5fc0affd1c09085cc3f5d0ad908402b4b36ed307b03ecfedaf4e7cbe1701000000b940857549b4adae67d0a798be8021bb11c3da69242f903dda0fc6e8d86bb5cdd7fa19fc174042d58d972d2c4a1e610d57c61c7a96748f150bbf0d04a3268203ac4a397c85d291afefb9385ec51dbe2087d4d030b8d69bd693a762721b39330a0100 +130023424 +0 +dc58bcd4bf3d440438d90429fdb017e09e6a54d6bf1d4c2f892463c6112ca19d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200a01a74813d93872fbd301800204315cd758b154a5ba3da05c058447e86252fac5ff0ce1e36e5cfe3910b06f7ab27a2ea4921c50f8ca6cefd9b34ed505e9dcf041ffea4b6a0578cc0d2e645794c74d5946f0630021d0d3313aef212a6847a9f0417010000651cca88a6d6bcf1104c833048a5a381f3efdfff314a2ae1aedf232d2e28744b322ceb001cf25c888fee0175afcc17e534307caab6aac03c943f535e6383a2169f07671e698bb317793fd7d93d2bd5d15052862dae5993bf7b135ae765c1ad3f0100 +130023424 +0 +1efdff493a6d83a24b0ca19c551ce792830c5acc3bd3f550ed0789fdb300cc51 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000be1b6be86e87e00fbbe15583edb5470161566e4cacf511a64d83af6d94792abd5b4f377ea56698eb09dbfc7ea04f6dee6494ec7fd04c363592e53d33fdedaf4cdf6f17669e5e84e5f971f77e6e95cfd099a205a1b5a7db181cbf4175b4682f1a01000000763b22fe83445349599bb9b62d1fb37052f197745289f534fa48d26ec6558692b1dd3b708e48f68ff945c64a9722d492657d6b70934de5e0b2f6a949f798a01f8d098b189039e992081b4df1f279864d8fd7244f0365ee5773159532363f1b440100 +136314880 +0 +c019b37fd84db868bc2101434ac2c0a8c7747be7dbe8f9aee4d4e87364861e74 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200169e8679862769813ebaa692311e1f140f4bfd869723502e597a4666305c6b1b74aa14289fc99ee91377cdb454b81a0920019b1920da093b59cf130dfeecc96a9d2d030744dffd7ecbac46ac6f7521c03297e556ef2d0121e2be0169d952f19001000200ad8d68df3570a43ffa88fee0a502b01781d19f5c797e236d2696d955edb61a9ee44a4681ac6b1f6b83c7802e3b9761fc2d25d627360ffbac3133878a84a87733a11f029dac263f57422ff473f37cda09a252fc2e7def54ae7526fecb9d6542470100 +136314880 +0 +007625136da615d234f9ef1cfe8300cab12d6bc9c312a664cca8a8dc52b98811 +1024 +0200ba8e6b6580869e774261c7b185e54a57f413a0ad04a94c79d220f963f652c61954ac7728192dcca6dc0db994ffc93fe917ed37a73fc48d6cb48eb5caa7846b5a8f127d192cc3ee01e4adea6b51a295ccd0b7d206b9658e0da4d9fa8e835f3e640100 +03008b10dbfb28efe56b8faf18c80c32c106e2ebbdcb9dd4396335019615766f4db99ea9899d70d5d13b891fbaa1c550a5d952342754f4f36d04ad64e3234c2291014130b0a3176b672294720c623a0ad8e6dd190e12e316f1d4bd84607d95fd0f01330e0200cb4512fb0ecddf184c39d8e3d0effc5668dcee6d77ae46a862e9618bfd568ab6dcd6309cf2b23617397503596825368fbea7cebd6b78944450bead8c25ccce1ab5fdf06690fa1f435964ab90ac3cddb04200b83e84e421d4b7f92386cd322c1e0100000000000005f30c9d225e62c2c874b46b4495242bd93c2b88406b552210238fd584f2ad07e647f6a901007aee257bd3a6a2ab5961a9c8b4cfeb761bc0d112ef92961b0187307cb6a0cac6174a795fcd5f77a7be534874a440be89b6cb406be316f7b774d2603ed6b4361bb73be9ee9328839b24510bb7024be1e09aa95c430d77fe3965c2cffc56a04b440100000000000011d8c087f051de9a1413ac3b24f822945330ae9f59b26e77fe24024c3a27356a840f976d0200836f2a2f136f234e648fd2084d605ea1d8899520ef550c2050f32a06f38bba6141240975115fe420195be9151e6e66f3f389fb67206b2fd57a0ba40831070824143cb0f36bf7b28d40ae779bcf47ea956bd24db70a484f4dedf277d69198c42e0201 +1754452 +2 +712dee7c00acd50fd47bb9a0c9a20f765ba8f55f07fb89ff1104b51bafc931da +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +01001bd3ac9cf06f33d8c306ec6e1aa634ac734ca5ce12b8b75b53ff4fd569e0b7936dc421d81ad1c7af61ae3b0e45a0d94f6708128d4341a46bf30f8b255982723cd1868221d96c08c88346a616d795d5053ef7527cd428cda15c7c888f5019b50301000000ac902b67d8cd64ea13097a1080629dc5369c2639d670d06fc1768b840d3366b6401f00d9f74c0398782d7ed93ca4d88af1dc8593ca18a7b7e3a97e56f6125c1829a5c3f1a888050a4d90f05cd78c02360c31dfa3be07cedb9424fea4c99ae4290100 +134217728 +0 +54e31f49c512355f659ef8aca74b60f15d6e3b510aa7bb9bcc64c1d03a8d8cb5 +1024 +0300df144c15f51c96b67217b4263e3ca4c61a6f9e59fe2b937a6b351825482ba8f1c340298eb8c37f9f2b9f2fe1162f74ec23fc45a4f9e1a94e5b554914e7eb8a39f11e7fc87bebfcf9c1b4561cc9f28f3f850910de25bcc9facb86978100a90e070201 +0300d62bafb01f67b1286584f9cf24f94f7851e8c662f7a773814ccbf41b24323db89332f066aedb0841e2bf871f3fc252a043209e2ead71bcad78542279ee9d7d2ea196d446dd159c05f4826f9337b9d8c294a39641bc2d10256b743781502f8f6001000000735da4ff28dde936a30b64a80604b339237e24a28ca1d66c72004613b0807485c7ff62aa2e444772331fd5c46132c9e1b28c2d1d9d3259322b3f5cacacee2a58bffb64bd21e10ac0b3d077478d7f6eb10bf152d726f72f59b169dd37ca43c741010000000000000c9004fded9ee9d62c3481332b16e201d85736a7bb0e1eee107c8d2c830204ad53337db100005b54adc626b782dcb9a3fced355e0a0827ec46d6df6127feb9e5b46fb5915615d4ac96185993657f383656464cde6ad0b45e19ee904957752bf21a103149b70305f817d7928a02083b38b34d5023934e12758260ed22c6f9a774f99cbb672401110d000000000025afa8b9e4925e0270e827134277a44835c137bde1e41f91e59602b6224f531daf66baa100009ec7aefa865501c2d12098e8ff8f54723f2ff1ce3b5c0ee17d2ecbaa6723dbc5dd31485c3849110dd31999932a5bb661285e3fadbe3792c7d97ddd4b613795155d404430530904b56a379426d05ee14905aa288962fceb5c07a1e03feee8a9170100 +3704777 +2 +d34ed7bb1ea6b3138789abb3bb0ee4694b06a9e14d571ac9064c453952c7d97e +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0300b1cdc1f27849eb3247dffad2a5049e016cd4c1198b2ea30cc2aae467238b727582e5d3b900768d71f7d9a2ed78e90d6045873fad64821849eb7aa220dff5dd021fad0341926c6dbc44af36e814c4e0a3d185eb692547b8a0bf3095e8c4db69020201010043c1312f8be73c513bc8508cfa3d740d4c4dffaf67075838647ec0f3ddb6294a87fcc06dd476daa11fd997f3bb5144eb9d98d66dad2c7f1bf0c776c3c8b357462ba12e0a1801948557dfaf7a7787a4cd2c56d48f363050b6de66ddbbdcdc051a0100 +135266304 +0 +00f58111007f127af91d30e20a21b7cb2ec99f8fd3d5ce4ff8c8de13f8b140d9 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0100ce1075afa2a7acbc1f9782d2192e06d407ae95555a823b69e810e5dd27c4e4dc143846f5b9ab8eecf213b0a3ab0322e7282f3a563609fe84470201e6ddb14f552fb842eed3ef6b56dc398623eb25b3ec06efaa949638c2c844a564c6985a3e2001000200501b05951d344314f493a29de63bcc980d27b4869cc7cad158a13c32b0672e80321af9cd9c595594781c95be1d4ab56f06c520f6c39ed3536c0dcd3bb425ee253f30e3a25dbc339322d4ae6f1c43d66e41179c120673ccf1b8623045cecdcd430100 +136314880 +0 +82e7cb1ce72f1864c8ff428b6a3df5199c24fb245682d1b151fe7fc64aa60b9d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000ae6c626ea13f1857b61174c6439aa66bfd3471c8e97544d3d3af7e58a6a6defbcbb8b085eccd1139848be652a06a51e83b5cbde7707d99e013a9e71bfe23bf538de5f1ffb285dae45517836f81d6ab0075533757ca36b62b2754c5f54484865201000100a5780d955aba2642d1abb0d1f1220e5334d2bdd51956f969620a46bc4a309f30beadf3fee1c6832dce9dbe721b6289d7f7edeb9b7b0dc23ddae0cc2a123b1e16a2ffaad42bd38e3adb80cb5b972e23ded8c4811d845a07865c1a78f215d5ac0b0201 +136314880 +0 +8e6428ea5395a2b807882ad30119dd9fe515a6426fe8431d9a99d20683649af9 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020079d3b977c06ea5f3a430c2e6b6113af2218fa09924bb8478eb97b00b22fb887da480ed7b4a9b493efac7645b0915279170839f39e0c5028f5d7787d480439c3475aee8276ae4d6d0700c08c3468f8d34439b1facffa2e947183bc8d594204e5001000100b12edd21efd69103704bcf863d06e01d759e13065277fc1e63ebe02039a044814050d339102c44c92ed61422e0ee80cae59c29515a22fbc5cea340c4251d8201ce0bcae7d9f33f9c2efea028a401f5ef4502012cfc713fbc38ab3d7ce8c846020600 +136314880 +0 +81f71b86fd3a4edc816ff31d916f8d97cfa01e48706620768e34074844eaed0d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000352f302849f5b4c791ca990a02a54824586b8617f44d1a3b77684055c7cecc42529475d62a956636bb5a1145ceb23cc9130b11c6ad630e23f1dbf71b587eac43eec83965c9de32e983bee4f8c31f6e8f9e3b770c346a8efe897eaa106d2bbc6801000000f66f1539733c52023885430cee3eff396256c77ccf303c7ba0dd8a350767bc7074c6b3a66bcd9d58f7d8d393cc68c6d7566af9e6696bc6bb4b7a26d0ec44d01b8b0b9a2c1f06944604ec36e582f375545053e13486e4bf4f9baa53a2c55291090100 +137363456 +0 +9104c5b5e45d48f374efa0488fe6a617790e9aecb3c9cddec06809b09f45ce9b +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03004b69a0d05b931884df11ed668b938a80236075efe218bc77a756134d218b53d993a7593f1d06928b700245c7dae1b91409138be61e6a4b5b326cefb5d2b29859e3658e3c19bcc57c97601cd488f0874c9704255369555b342168c33f2088db7901000100433c6fff6a6258faf1e102ebe6276838c4842307abe25e3c9230c2ef8718189ddbbaf4c263db881bc512596bdc04bd8fcac312dd7a1e40dcc19b859976d97421837d101315b81fa1b2b1e2b061867f7dc1d919f2577779e59f1ae6e92fce4c230100 +2023424 +0 +cbd816190e6bca52fd27823958e2cb83e7c3dcad803170835d76bb21cbb6bb22 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200b5126b4fa9ba1cf20c7f603142b7f204b1a626c58c1b66fc49b8c0fb66e8ddf58e7d6f0bd0c0e3bae63915c8122d5d3e60d5793f9717fb4cac87dfc2fa727969537cf545229d69ac7d48017297e2df3f01ea6b1b04a9c00e474c67e30f95ba5201000100e5d0dda194bd7bdb5038bb1fb20cfbc28ca3a2c2bb1e56170806e6817c8501a60e0fba7ecc42ec7674fe92b6d1cba5e68909f5c9c4c56aa2e8b22db19ed5586c5deed5d90505c51fb26efb5953a8509aea895b0895806665e831b2e600e4ce760100 +843301 +0 +9104c5b5e45d48f374efa0488fe6a617790e9aecb3c9cddec06809b09f45ce9b +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200b78ce40a76ad02f713219cca374afd71e652ab1dd678c6a80da592642897d9b9ef784a73d54ad87870e31d76f828166186629d52f590e9d75514cd2671be9404bb70b5df356791da67cb30d97660e45a5fafed0f6f32c3378b6d74bf7d625102020002001f812f383989fabee34bdbe494a62cc33630d47b141947365bd9688c5bad465f9f7df01d5508607cb70a1c1823fe8c251fcb77df6d35d752954aae13287ce806396867842555b421227f23fc138f507e8307213bae3b1cfa5308ca163f5fe9140201 +125452288 +0 +9104c5b5e45d48f374efa0488fe6a617790e9aecb3c9cddec06809b09f45ce9b +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +030085f813d1a2eca91c8a4341e95edc9c0d60cc7afb3e2806550292ecea5935dbad4ae27cb8fc6ab166113e1c5ee2150642b765f06c70ae4474963b3f23f86481037aae4b33826bac01ef86eab3b86c7f57ec7e24f7ba46f1e21d75638a879bf1020a070200ebffbe8df7cb684ffa2ff08b85b29654a7ae8c30dd7a7c3cfb4b4a22074f065e8dfe1f448f54aac2a44be5ceb1f67d1f8cfb14a4009835b04d6443cb179b6827bb07fefbc03f2c94376e5de46d824c5a111881bcfbfe4af88b338dfca223dd2f0100 +8642368 +0 +1d93cd89fce1c1697ce7e7bae477afe15224eb65ed28200b05c8e9df62865bf5 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0300ec136b9147fed1aaf2846228fa45097ed5eebd64212c9fd4d7bc4cb7d534acc9564caa307a74da28163a0dcfa98c143bce8ee5c46df863561bdeade1d89e08048b1c2c0c7372a57b85044a57f79d2adc2aca0caf3ead77c9d10b17f72b25d5170100030087e9caaa42ca6f4c1497e5d598ce69aaa0f8e005736ccd0813a449d8ef1dcb04154c6ff7400434548c99037750e3623273a06ee50284277b3af0fe9b26baee1c121a1e61a2ad70a7029b7733ba4e0a77b392ea85c5fd95e0ba4f683fa7ebbb3202010000000000223da0c0791e0174992501ad3321f151fd57a0e8d0993aa63d6fcad69b0293849ebdf8f503003585c17a80c05d7b8067ca42ac85d8de2913515a72dae2d28133caca79670663488cebc9f10df57c1a843947bb8b85ae52821426ceea2234209ad4a8af2ea60855d1fee2a7abb8f862f11f8d07eebd0a1c08bb5105209616ae8628649d23120a0100000000000066d820bd69ffeaf112a255c32d7ce7d3f916cebb3d3ccbcfc41f540c880183e857c85dad0100164109a813ff1f97477b27c412c64ab7d06ed900d8c5c4ed01054995c99291e48ab30a96b439ba40556d23352562e7eb87befe8af3d312fb61deaca47da3550f6dca08549d0d0aec5ab36a062dfed01274f79824fbdd428944105b7856006f1b010000000000001cbf609cb10b18455417a5133669010013114a36c73cf7ebc5bfe8252c8eab1bbe9a2d810300978bbe4322c4a49e0b3126d83b27baafac9fb4ccac613f8fb747bb5e0dbbb7444e9b50628865ffd1910335050d569e62d13d8e29ccc02e6cb8590d60f959881dcae291cf4cd72045f3fb8bdcbb346e84ce02d2263818720c16c8f19b41b8ad0802000000000000564dc0fc387bcd5c1fdabe2aaa045db3ac4b6a4a07dd20462531af92fbea78ca5481b51d030049c66458acaa55747803737466c798647c9a2392e7dfe0eeaf61732b31bcd52df81a943c1261315d12e7b8b000f06017c0366d08ccd6dec85baf3e1a2a0ab2054b953c82ec5671073ed583abfbbb80fbb7dac49b123bc8923e55adec9030fd040302 +17308809 +4 +cbd816190e6bca52fd27823958e2cb83e7c3dcad803170835d76bb21cbb6bb22 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03006cbbf53af63e4516fb66988b9dac96ccc58b39ad57f448312e71bab0b04a89edcb44ead7dbdf568755714386ccb8567eddc8336e4d2efa38f157c28063d97c5cfbec6a6e13c1e3833e956665f098903b5b27f9bc76118fe96836dd8f9066d35a010003002bee28a14e8088b17264754fddff4082b6d5c102eaf31bf9741c9ceb958b64a5d090b1fe976e50e19dae8035d042b97506f5e230237969d2ce782a2e159c310ed1b2edc2760359dc478bacfa02ad74f88bf949e31cb6a0ca17fd6bcdc70153000100 +4606967 +0 +cbd816190e6bca52fd27823958e2cb83e7c3dcad803170835d76bb21cbb6bb22 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +030003a85741fcbe59d5f965b9e3fe9cf06680fffe5624efbdb7f72f96d4bb7ac30732546651bd26c9b71f846322aa60f768c5851a75a1fc5cdcebd223b5aac821031399eaf95a3de5a5e4f173ab83494c5c43f9f1f270b1259e936b3eecb8a75c0222160200d0bf6705f7d97dcc4d17e563ae431c041813a2ca99f9a4c8c4ce85b15e06a8d4d546192c4085714978321df00f7ff4b6c133a7958021f20c40c6a3de2e29ec22cbd9f3ea88ddc0835a8e43b92c5b11bacfea379d8e2534ddde13b837eb01a6080100 +3634889 +0 +2d28763748335ea43745f47ea4e9db3ebdd07de623fc06be3901b08b39c5feb2 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200ac08784fb55d363468753ea24a9900d864202b084bc9f718599f78fadb617cd6a0aa566362250af9ab8570a60eabdc08b0abb6107917b10561b7864fcc6e2d092d82c2ed2bbb9aaefd107b00ac048b8fe8b62d141722a0720e0026609219690302000100512da50808b946a764c54c34d5f9506b182015f265f2d1e32eef1198a6f2647d04482a732a5b5a7766460abdab942a0e4ced9c29e2826010ab272caaa5ba9901b699717319593f0161b6354a9f59b37af7809d7daf28328e8c272c5b5ada41050100 +6094848 +0 +9328a058828f36dcdbe5e25cf11db645fac8b91e3db68c19d1ccb9ee7b1dd618 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200ec2ed446419164f07db54aa2d4e6f14483a6dc25bd1345e785d608f787e0439f88c4bb67b7689056b6d9d9245b21c34b35a2b91d5fc0e40d44c066ebe7f49c5c9543ec0567225acac29456e3151f9e828974b4caab644f6c1001c27d209dd019010003003b31a71fda1d1efb9d37dd9a93da40f91532f845de8132ded6a74ae3f0cbc48853cc5c6441ca51c09a54e1fcdcd33c19ea24d41bb86aae3a249f5da00cb61811289b7dc82f1b5c7892ee75756e1ec12e65ce007f84e93999e2bf4bc0df9fa8140100 +120856768 +0 +9104c5b5e45d48f374efa0488fe6a617790e9aecb3c9cddec06809b09f45ce9b +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020085c1e1459d7be8005580d54f777d4cfbf0491e750ccfbdfab7280ac4654fa076739ac626c5651dded00391cb8f64a9f5992e806c735d10418ab53b02f2d7020411aa4ff038c5c969cfc7c6abb9597b3c0c48d3a8bc0cb167a1295d70836c0006140e0100fafbc00e293a0ef0b964c7fdc04a0e2c32cabde4087a2b67de3e636c02956e4db7b8c75bb219ec2480fe886631dc1535e860589493763efaa3f83362b2317e70e1ba22584ea4b18cc8bfb2b7b9c5862435ec7880e9a5d147437279f6e4d9eb330100 +127475712 +0 +9104c5b5e45d48f374efa0488fe6a617790e9aecb3c9cddec06809b09f45ce9b +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020024490803b42ae2bbb0addc7f5a9228c2985ee190527746eac3c17ecff416d9f0115ff27ed31e52bb509fecfb90d169b9d9f33f91b08401097da3c2c088cfde04854ebea1b4f353045e6f7e00d81788d2e7e21767da865c4ec8b2fa4ed900301801000200639300f6f986b5871306ac76c491b5c1f98d293a19923c4fbae151f7e61408fe48b8b890bce5cd9a84243530543a3c5af672036934caf0639279fcc3872f955e46f164a007adce7b0005d869816869d49fd3268e80b35928c395409dcb118c640100 +123428864 +0 +2d28763748335ea43745f47ea4e9db3ebdd07de623fc06be3901b08b39c5feb2 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200e4864de12f671eaf2ff77bc6674e82a7cd65b78ae3d17041fb776dcec2b8a071c6040af0800f051a3304517c0065ca50593d776de5401a250cf204279d24d542d3d50c28027e1331d89f3768a0ad1a0612a1aa910dc473d867a1f4cc3199a87c01000200adf9ca8b3ab9c4cf5ef245bd87a89bf69a0ae013d133d1cb375d9cf1c595022dca374ec032a28d965fc29e503c2a850b89e4a3db97d481017a5c8f87479b250914a95f6824286e04458a4f8be126aaba62da1f9148c0982cbace1b2fb6e925160200 +13277711 +0 +20d764df07a6caeb028b9ef16d10889802f82ef7b9f82fdada3b66ec57b091c1 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0100a3c70178eacf6ce7f4e06ff6d470c8f34dfc41f23d407b6ec4b15ed446153e7abfaac0a65f56f0b9feb79aff649cd691a292f2fa0d5247241f7b2acc34579108d32a00b9fada59a5084640c795a9e66d438fed386e15cd0665e71b8f748ef0000b0403000b7e15cd4174190f90f14e9a726e8ff95b60bd271168d59486d25f77fe86440a544c98149435aa12c22c1a9c170ccc2559c3f472520e9998b0aa44add345170fc7a1b2846434418c60df3d999a0f4b69afdfa6014b9b604b21952b3d9643141e0100 +116745713 +0 +2d28763748335ea43745f47ea4e9db3ebdd07de623fc06be3901b08b39c5feb2 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020092efe6639c2b436810794f284e42ae733e5c38050b240f14fd6753eec531d02163be3dda918341502d0c3f374ffff3fe66c7bfb91b61640f23830a7cd837b60267f0bfed0096d4a86c34d33aa8e0807fe8f431afe6e392b3ed604cb0017efb0408020000cc828b9a340a91f321d9913bd3a4a6479233d8ed0d1646bf8ed3ab47e22317a1269d8e1ecb26a80626a5d52218a848fd3175d0b82921874c7cd61cafe0348e437bcfe2fd6599846ae2a6afd78e9f6390c1618eaef68dcd7015224429b350b8560100 +125960192 +0 +f2b06664b940c99631c799e30d8fdedc6155e0f7bed8687f1d635d67a091ade2 +1024 +0200130fd159bdd854d85bc6d7780d503ee5a530be020a879b34e69c757a791740ece08301813cb3b870aff210ee80ca0ad8fcad2da9f16c9ea39bcf73d521c4d417bbd417664dbc26acf441a2798c934c46cbf6c0f384c0de7d933947a553fdb02c0100 +0200b5124ef07f5892a66a08ac0eac20823c9c67759c32938128aeacf67c80164cb649c2831999cd4cab3375ae9b470f75028ff7d6504131229f865466ef2b6ac877dae1cc2133a67783fbae1e3ef27f42833d81c077f4420975b33971888774da1a01000100626d47b3facd599315b4cf0c4fd2553de5b77ef79fc18a3736ad7b62be6a515db89825eec5c9bf6dc6cfe5dba22bc3288074b88ffb18f9d1277039939d01b60e41cf5f708b96610dcfc626e5b1143784b55412529c201c19edad813047f9442a01000000000000223da0b9c59388a824c6bf29b5e7acbe13f74edd2031aed8682cecc26d44030ca0f707bf0000f6b97981b5777a871bdbba182ec986189dac50eaa16ca8ece06748964858b9c1dcac319031c271b15e465ef41a41163acbd37546ebf8c2c46517543e79615f5843859ca4a83f7ffa4b2833b0a9586fd92fa397477520e4aa1f175d16115985500100000000000066d820f0692c6026f18dc4548ac8d61d0cddd4faa01ce53399d0da8210e7a2de28454e93010077ed7c9728a0989d043b5ec5cc7e38ccd78b2bbfca81aab246878de22dadbbd2baa2fcf609130107e7e6187db4596706c329641f900da18e90888f6d17529e627b2c5c6a3cd4047600cfbccf2daa26360c8c7bc05331274645a3cd79e7d7bf34010000000000001cbf60edddbf30da9c2668427dabe8af59bc24b5902e1945b42a7031ebe940ed310449e901009ccaa1f1578503430eaadeff439cb8774eca2544b1c061849928b8e66c92448f8ed485677d30560fda13e6d253dbda4c7a5bab0fc65abc145d076fb314e778538b60f450d227babd3896de29253c0308025ee7e6631ebe59ebcf00d80d97b66e01000000000000564dc0d49209df78249fe72da0bc77236e18856fab05b568a18fc21eedf23777bb14e1150200980e0631586094eb821d287d265f3e929f7baa1028face6ff1e426ce1dd185a918d8b9e33853473e10646ddb3d46df9005802aead8873dd455df1017ec185f74a9659b9d1f90747b364f98fc1205900226891ba014c5a5577ff93cb8290c55640100 +17308809 +4 +dc234a8cd98daf561cbe97af5167aff8feb9ee271ae6cbf8d3b19c33a30364a3 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0100a06c9b1475c1a801be43b52c8a521982dd8a98369088bb5b17d65e9c0e50d3b616e07e2ebc15a59f9b80bdea4afd5dba11295be397b0c1d26eceda322d747b1d6347f8830204023ec72c57e41fb83ccb699e6a3d8d4fbc962adbab44c071405001000000eab8d844027adafd93699103e21fe7d870879f31ddc950e8618e63bae6b819d7725c5a9da5718c21fd18e6f562be7549e57d5350825948d2d940c51cad23331daf034c1415631dc8dfacef6050cbc6e73ac4f9940cd3c45f425f7dd6511c9a280300 +125960192 +0 +dc58bcd4bf3d440438d90429fdb017e09e6a54d6bf1d4c2f892463c6112ca19d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00008e2675a8bc6277ab5a968d07ede79c3d426651d1de21d15da86f3beb846f1db54d3d08fce698244034c8f3870184a3aa6351d30ce83857e73e8bc9a85dc2711b1335915baf92679c01f0916026b3138ab6c6b95784c7fc9fea46bc2b2173500304030100a8ecd5c9e8a1a0c2c1e124db2a94a9cddd5686c4d6a0337bee10d3cd25b04674a84af9759310eb3c8b7b2781b7be733da8a253271904038c5847601804a1947b737967e2bb0479b691ba92dea7d7dbfae9bc7df9b47999a15125a667c7f5df1d0100 +6094848 +0 +4a03a91649358386ac92f0fd7718549c9196eb216a38db25474c448000c47ee0 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0100b460b226b06f3ddea904ad0852c16ccfe6e4e61cee763775e02058a80bb1298e83ad9c3522fb867082865636a609dd660158b26c0fae36ed22c873e8a8dd67102f03fa4e42677b5ebb51ec0f84c923a86a6262b20f910e752742a38235543610030003008a306d9b1142ed9f273484b259ceb92933b4746841e202918d0a708809b93b5bbc00387575928e2ee7ef772bb55861fb7e77acf87cdea38bbbaf318c8e4a5e5029e08e119f4f3f9f5afcb6f998ada4a142511d5c26fc3a400c46290d8fff282f0100 +3078353 +0 +dc58bcd4bf3d440438d90429fdb017e09e6a54d6bf1d4c2f892463c6112ca19d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000d3c33b83966d108ea04fa3da4baaef7ad1e3dd42a6eb6c2c221c7d349673f29b8cba8970b368f216561d8fb5e1843bde9889aa424b04c5cc2fbc2505d4d89e2ac11729e6f822f15d189d25a8d27fda62c68bf8a3aea867b22fa820952e56862101000300f1685ff7763d049f68e38545360e0ef8ab9786e903870ee6a77a69bcf2ce1f8e421f64f164709571ad8b20616c3cc59e3c8f5dfa28608476bafce7ac8cc5ed0a9352c6cd395d19b8e38f80d8da74685e72caaac59b83c0970895661a3e1aba040201 +13467297 +0 +e7c0271f045804ab36581d7abc3676dd4318415e65a277bb42bd0eda6e221912 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +01003c96e087447f8bd0761e27fa994ba595318b03d7acf42fe0df9ef9768f78fddcdb6f8ceb7b33b9630cbdaa31502497f472128ca85e11b1a0d49452e10d0bd1465d870faf7606e2b118ca01d2877e0435b65c50c147e6d16c2ce1a8d1f5e274370100010054497fff6d581eed53d9316f52ddfcf0c2fcb0e4637fa8dc17863f12aff6504066506c2df82dd66fe805d836362ca201f1464fe84ff1d366d363c1141f3af55d4b9f63bef7529263b8fd6d8919e03959ed12e3381951ee1d0f3a6063ce30ce4c0100 +3993257 +0 +dc234a8cd98daf561cbe97af5167aff8feb9ee271ae6cbf8d3b19c33a30364a3 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020037828c3f520b102afc6d16b9c2ea1ca42f7d0ab4f5125201268341373432ec08b72c427e4af116cf1df9b478abcff17b5cb7fdbc0038944941f9d5737414215f7521538c6594c4b347dcb945f154515d69193992a8f5fa3d68e644fde79a3e4c010002003a2f33d988c217f9a2ce276426963d11da90923db63c43647119b5d662d4217727d7ed7d871050361dea2d60c68f67c21a2e035f2fe7e8cfdeae0480762b5d6c4921f3a86dd31eca4bfbd2fdaf9ef75fb97eff8a1a968db72a390b5e3bcc924d0100 +6309256 +0 +6487a69792ad2b382a4d012faa92b2f25b38a3f306761acb3aa9bd6edf1d3b2d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000dc1c6761423d9d3a2010f997d9896a6b9dbeb90316e0fb81bc55111a4d4ed5c8dd7def0304e88a8b871f4de6cb26952de21a5b1f896b0e2e68fb235649a17226dfad78abd47d40a141f3864da24da41d4066dfab35048be9a38adcdd65936e1e010002000e3f3c900539d2bafba4ac0d273fc2d2be866cc5c328019bf358c567bb4d80b7233ff5ec09f89403922d2f9ba2872a689efe7a86471a5dc35c0e6a35b5feb21e656890c22cd21218eaf65d9290d431d1e9f93b6f428a01d6924d8bb12226d2390100 +116556127 +0 +dc58bcd4bf3d440438d90429fdb017e09e6a54d6bf1d4c2f892463c6112ca19d +1024 +03002dd1ad08755e114d076551cb3fedcec9d8a2c42df6fc606ef42fa7b04d1e07adbebd80446b9948b07e20e20a5266a6bf3e990bf13e86ad648881d9d1ac0b554edaa1cd81059507cc58bd08da782c613081a743eb3139863031d33376e2452a290100 +020021b0ea24bfb7f869daa2d5d13b29ba379ea10641c77b65ec3a89ca151184d21ddb160ffc19d017dac47cfb81bb3023f178fd85b9e116e6fd677b183b2eeb746d9dbbd98f33aa52fa3604fc27b6a934992d726bb6f70d1d51906e9a264238b25301000200c184670695121ace660d6f94dd497d5ae88a1a875e60c51517aefba12203fd128130d3cbf5000f611b97caefb3f22afc67917951d47019dcf7d6a2af6d260362c9ec63e4b69f3bdf6c4dec3521139453e93ede942cae5a8ec540c686e977c8410100000000000009c978e50f8b8dc68d89313bc5209ca420693c3062a36ee6d5ef0868959fe7f057339cfb0100d1e0a4aef836d65c430d7a188f4c1a0aa8b6faaaebee2e759376e117bcc1fd8c2f2bf6b1502eb524e5b03bdbcd8375d18d12774cf2b133184fdda3c115158604be27b63ad7818c1912edd92851ef5adcdcf7150955a3f638783b9e2cbd5bbd021a1900000000001d5c68b5a1d9f8c4d5e12719fb09084944886930b8808029dfbaa6422a0a4d08097e491f0100f746ea4823d3442f58f2f43173394510c77fe77b769a5c22c9c8049f3655cd238bc1e47ac2cfd357cc8fe049609e15410ef76ce7af9364869dbc2f2dc9787d3472936ea1fb63797adbce8c0801dcc51c366fed51ba785b15457df133b854da4e0200 +2886302 +2 +1158101517abcd7db595890d71ec013f0e208e242b88a6c6dc743938833f6a50 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0300aaafd120ab7c1215ba3e8f89c83a2c7aeb657d4a1df08000b0c4f9410953fd22e8c4cd22f1c2a116b2d397138d73f6f9f60ba859ebc03f08ad2332d57709f468f5ff6819e4b1d8e1d99554ed026dd6ea08c89ac9515fda72195c54a2d9d87e2501000000ea39907bcbbee078ae9f0b50e65864a6cb9f14cdf81debc26bc5a73fc6a59b351900c6ea77a36fd5b7273ecfe6aaeb2b00b4b2725eebea910bf9a403037c1b22bf52566ab496b0df0cdd509908ecbcdbccbafac5469b09edc545ac1725784f290100 +123714168 +0 +dc234a8cd98daf561cbe97af5167aff8feb9ee271ae6cbf8d3b19c33a30364a3 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000041893432212b50b0fae89e211531e18dae55c3eead351d6515bfbc04ae36ffc12117834b66834b12004d39c65de3b520d717284031e71daab1e69470978dd705d13fbd444907d046de4748122a26423fe6e82f7d9fefd4317ab33eccf1e9ae0407060000e5e98e2c6f87004134c781b591fe86fbe8f4b7deb1f337fc01e1e2a1c1e6af7f5a47eb3c6b5822946e95644d9a33526d44dc7483429b1ddbfab185aafe9a9f07f1ad43bb39ee2b7640e25f55da3c0a5f5ab715ba5edef1e1d42913921958b90e0302 +127991808 +0 +f2b06664b940c99631c799e30d8fdedc6155e0f7bed8687f1d635d67a091ade2 +1024 +0200aad17d7935d43588aa9dcb7c4e2b6822cac040e7990de69397074aef57dd0afb2871cb602ebc9a3db01c4978cea61ad758405fdc14a3e18538d1874cd519166b013a682f4eda8b3ac47b1dbcc340ef8caa72042dbbaaa5559a378bb3b4f0469a0100 +01000604cf6c438eb79c48351026bb78e0398856dcdd1a1ccfd136247d1c459926dd04deb3fb828b08be3a056cd75522e1236b1ba19ea411bc6b7c86bd69ea624b1fdf0ee440092dc4945635690042ef092fe707e921e146b1475b4344f6d01ad820010000006643991eed053fd7c37657964eade50630e07b95a9a497753eda6ed2fb5810c60872636e12602e99fa6e8c6b38038e1d4cee004e220cc75fe8dea12e28d19a0027c66a9b56069b241c1f856f459e0317a5a4c96f61789134c647ee38a418af07010000000000001bc560bd5d3c3e0e09cf4ccfadde93ed8a7baf1243675b0923dbff868a3e05d550d2ccc50200b3588419593ac7eca0eb7181ec19fa9f9a37e0c9563baead48b965a948a08f4fdcae1fc8bbf284625fe59db59e22265fcb4478e3bb6d1343c6cfb2d5890fb41452df58b86a732863385ae0a90fe8ac5d85108a3c702a120ada094ba8819236230100000000000022ca4083bffa3b34afb6152e9c1598d756a3543d433ff8c42c55f21ebcd9311c75e9d8af02002f3cc24f4ba78af139855de0dc3616f91d8e33a28fd58c9924f8f657f52f7f7c2c34a08d0b0b07543fe002f8f68449ac7ec769de512bae3a74a3617e6f208110106f0274b36eabd20a0284d88cb73942c9273c2c9e80a9654115cd93b8bb590101000000000000684f20a43b7ad02068b489bad0848efbca476bf849478e7e5b378e9cf047981b1206380f020098bb715d336321ed0dac33b1ab56376bedbc66b8013d6126544ae2f744f455c51c1c8d2beec44cfd4a54d50bec87279a62dea13fbcd054655d78dd2d6e165e713f17441d3e5e0319a0944b16db2ee0edc42fa3bc10d3964a241ebfd1681bf96b0100000000000022e980d3075721b1297cb518b35d4cd55b4fda8324ddc972caeb3e57bf1f4347e6ab47e70100f3b940b170ebe374bd3a1452baef43913ecbabc82553bbd222a346a6c8ccd46addde6cbbe0b0ece812f982c42ff2316769bfc742cd2eff0543c8aa22f1a41e10f0db5dae5bb96ba0037f2e63755e23c96f27bb0cb61b96305170ff4116664f010100000000000068bc80b494008f5758899fd016e2bb949ca368a1650b0ac0c9938797207202e7b93e40b50100f4656745ceb6d1df3077d19b31e2b3e23bb5baa9d2e09aa4705f665d5e0e079fde26f3f4dc2740453672c362c25812427a76e8e2a2f6a874df0e75e77a56902d496f496a1a9066bdd39388efa8980c18fca826daec87ba4f28cb80f17d3b433f01000000000000265480cc51566cd64ac9da40b27dd6614bf395064fec6239ce010d18d0f4891083f44607020093495b39fa1d8a1b8dd9b3a255d0cbaa9f32f371bd5820d125e4aef22944f2457a8858ecdb5025de9e08c86fa4dd51f8e988cb18cb4b8d0cf1ed3fe5db9afc377be0f721f1cc350bc88199510c961cc44c613c7018b6d649874f58d2fd2fd9430100 +23206780 +6 +4a03a91649358386ac92f0fd7718549c9196eb216a38db25474c448000c47ee0 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020050e3dd9df1db20f18e030de2898da2caa22fe25d6db00c5303066c8c116cdfd7509fddfe54849677e0cdf65918020b054d9b4cf73e9bbed4f2eba020ce599e329734706225b46e95c7a2f7a58d8cd3ee43fad5b1a2f14b73cbdbaebd9b7b570702010300844609f55ef6c161868f287a56176d54afef41cce2abfddac64162f14c476dfc95c40c0043eee365492ee506d5b1bde6b8374aec7439e5796fff9c1720e5fb1fd1d9609dd2a2a32c3b8a01fc7a7296922f600d1c52634d5ecf6c8312a83b27150100 +5234787 +0 +530c1d78ef9fc6c006537965c079dfab5df46cd6639e7ea840190d14b5a0f9fc +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200575beb1280021c630bddc008389fb0aeddd3d0949dfd7417547c57affcb212d5f25c73fa9585e2b4b308292f3ed095a2b25d9b4bb75fc2b270103f52af06735299a9031afc4b351878e2b820579807167fc7f4a66989e365c8258dd8dee5944001000100403e487dcce36a9edac9bbb6d65c71cb8594222995e25acee944b295c1868f1f3f56213919079451e76bbac295c4769829c4e1c2db2307117e93c4f86a38c83aff249830320a4045fdd501d85a52ee0b1b4b78fed0ba17bbb01c0760a3e9e43701000000000000009a4cb76e806f8750168e2006426eeeb8da28f056d298b54978efd588516a527e293275000080df1fe0fe7c5a26904a639c7346be19fe5391c3afdaa886a0310f25bd71e5a962966d760eac8f8ce540d5c365a6565aca7bdaadb8819874fb54fb44cedc4901db4abeb2a643217c53f844eb81cf6f96b9ae3cb7df824fe9821f96140c3008001f1e000000000001cee493175d420ce491ad4256e1aaa791fd52876eac1001121d7afd317040a68717e23d03006082f7890e7017b8d2b1ebf898429f60fdc5b10535d5ac6ed342bf6435d6b198b06d879f7b4480792acbce64938673781726240d90830803d4f38a16e9412b244126939477a9080e53380b87fe455ed0a1a7f1ab74f3a75586e235af0245ed430100 +177756 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +01006ac90bb74665e67539dc441610192390390167a2e81b921e16a075af5615f05c8d2ca9eebf6e16fa75bcc6c729349201961b1fa2c090d3ded8883efc3c3304334909c3a3b3ffc49cb6567912c94f6ffe55cbe6c589677b2609b253b9f7b8303d02000300ef7cd748c3cbc66ef393049c5c4f1d28ed267c40ec1b2a8c5b515fad45eaa20795b51bcb72ae1dbc4bff31f80b82a70a893ba583f395b991c0687cd78a4b7355405908cba6b587d6a20be749b757f1345f3d1f98c95099da16ddec7f38363c3d0100 +11235932 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200aba12a8928a11cbee04df6882afec5ee0167197c8e71e96c2a17a4fbafeced8be1d2e59bc14127c4478fe84e6ce6c8ebc7ffa1c9a18a379b6c6b12ddb8873d5628330d081d9de2850e6a81c374a5e4a28654272ebcd034513da2bdb08d67dd5b010001000a16c858446c8d3cef8e5ba9d5ba7c25c137659cdac57be705d34326c50b5a6a4b2e3bacccce93cebc61d15288e9c74fbbaf76a1d74f6971fd65c0a4fac91224a1fa9e0aa0b5ef01a90ba2735ec9265cebe7a14802b3541148687b813998ef1501000000000000064dacfe99f1ec894782d98ebfa023601a07887ea416adaf0c5950e987235274161c8b330300540781cb1178e59005a56cc37742ae2d61d5b2425867af35c2a86d2a3d27e64913114f52f9675522bab88259e08ffaa99c6e4d7c9f48fd20e335dc1021e9f44dd59c5317354bd176ccec4c45aea1b39fe5e4be82a5a6262c49ec80bd80e9b44d0100000000000012e9048ae02a1468e6b0c0ce6cced208b27fc1c6c2d9fd24f446337503250e3ec56365a30300a2af3626d55b8985ea4fd7d47ef42e366f51b99b241d659a6bfebebec0511eb4f44c9378da9c8f70e235e6426f9e5785ac4fec6ce4c1bd66c72d8af41c53665375f7d92d75bc8f9aebd60e6fc90a55b4a9cb69d4837eb0080e5c9b487f3a88640100 +1859085 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0200aba12a8928a11cbee04df6882afec5ee0167197c8e71e96c2a17a4fbafeced8be1d2e59bc14127c4478fe84e6ce6c8ebc7ffa1c9a18a379b6c6b12ddb8873d5628330d081d9de2850e6a81c374a5e4a28654272ebcd034513da2bdb08d67dd5b0100 +02004fcb2cac3fd80aa6eb1368f4d885e2a4ab90cd3df2a4560050baad39928dcbd97351f6bcfd3554d010bd0cc34bd253e9f80d476e7ab7e01d65562f60bbd44163c13c89a0af82a65be26987c486a1e1e370f9eae569765689a70d0003eb6d167e010002009aee0710e8c6e557e1cdf1413edc38df5b6afec0486f4d2d3e2fba853b396d636a174a64aceac6dd080bb488d325e1b6116573b7987f5d05ab9a2c0f6738d2522529097937f8b0609e8b3bd422d3dff4532c28a0711dad687d66252c9de01007010000000000000cad50950f6ec9aac9adfd1afd8c727350536d61de0bef003b1ab0b7232da7ba0dc983770100cbcbb29bf95295df0daa02070ac1e09348570f11f98ec4fb8be9dd5c3239d71f7dd338898600160fb01c4c451502218e69d9c6937ff1d65ffdb5f61f7e151832096f21d3060f461a4e545f6d15103fad50b850644f9c71764721ccdcd4166a5b010000000000002607f0895bd8466413b33a59d61fb88ffbfc7b4522e99a99becfbaf3cc8846091116164b0300f47d2302bdbf6d9525653d4a9d50648d34eee96944d0cf9c1eb385bc1aa2a2eceb5ddb970bd665af2d56c7b44224237e0cf55eb6d4d6a18f0eb88897e9791d0afb229db93d34f5e3cfa0bd24907d36fd8739420d0f328109e66c49eb1371e8080800 +3738644 +2 +dc58bcd4bf3d440438d90429fdb017e09e6a54d6bf1d4c2f892463c6112ca19d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020021b0ea24bfb7f869daa2d5d13b29ba379ea10641c77b65ec3a89ca151184d21ddb160ffc19d017dac47cfb81bb3023f178fd85b9e116e6fd677b183b2eeb746d9dbbd98f33aa52fa3604fc27b6a934992d726bb6f70d1d51906e9a264238b253010000009bd5de72c135033e0050f41a35e9e4cd1eff96dc26373812e245091eb5803903d8b8990afa66a48478db4f9ec492b2a9bfcea45a304b09eefc24eb4802775238bdc14514746b3094e39f14cfcca43e98b575fdbda7cffe0e0eac397de6a3531a0100 +123928576 +0 +530c1d78ef9fc6c006537965c079dfab5df46cd6639e7ea840190d14b5a0f9fc +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +030078508010293dfe201f1df5bfe167699b05aa68c0b71574681d95cb3ac31d7c1481e79f4b7e9961ea94fa1926a8f5fe4586fb246b2b4b38907b2548a80d30a63dd508bf5925f053dcf35f7b8bd0770f7d2dcf8fcc64b5e516dc35bf38635a34480100010005d0b43325b9ff1ec657b6313c15dcd8f3ea9719bfb8c4efd310a4c2b286988d04a569310191d0df51cbfa58d10473d998c15696ee2fd9f0f4616802bf332a1f49b1fe0a162a3e8c17beaacc31100681eb6e4b0fb411e0f3d3c891b7d766ec450100 +300828 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03007f1ddf163ecb10e506e43b784c50b07c4302477070efd882bb8ad089b36758fa103f471ac3bc14d73e979954638f66a186f77f62fa1de183471584dadc12be26c94623001dbc881e562121898c81995872069cb6604f2bb7c89b31798775b61402010100e79362ebea338af42a9dd6728da910432c841f6138058da1243a19bca3d8fafa2b24bb7e5aa2e6b654a9f815a6cce68fe1f3d5896b6c273849c840c75a5cac1f06e76f1bc1a6ab0f8c4df2c47efa659f3f6deee7ee6f62fc5acd2a914f65550f0402 +6389760 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03006b28c6038994e8f49f9ea4dc7ae09e22099c4001ac6679bc9a94713dec5bcfe2d3f93dc8368bdb64613493afa3bb11ad0228f85d09375ca4e28a155952cac90f7ea09e85125258af1f0fde0d54af7a909489d71d7405f2b5f6b7b0108d48170608010300c6f070fa57305b4b38fbbbbdec91d8003c39719837c49fb4b2fb97cbfad7b96671afca333c81233beb33a37bf1877b92eb02dc81da2041a3f4791172e3729f4b3148e5d819b671afa392726b5eb3fdf40145b8ed893cc511292e7767a14da5150100 +4259840 +0 +f8212c33a65507b90a7b7b11ae00ad97d6675ec38e3a80659179cd7328ba51cd +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000768d88982931bea596702b2e0aaa78069dbe373694617ff9e1eefdc7ac856bf972bd26e64196d4fae2acfc6617951d5e98058b5cfe265b06e9d971edb431557273b574488fa67673cc97cf532155285b3075dda89b6b9d8c98f0ac0e42f25250010000002bef62f60ff343987b5a6a8bd5c486d3393804d10b4c603f79a4bb9d7c93d5f579247a9addc2cfa358ecacc2b636c88a8ea06fc004358f6a1f5c2229a44a0a00d48b779f310cc56b734ec089f3a7a7150f99b6cedaf20ca3d53f5a5de0bb7400280e00000000001bc560c3a4701661ddd65115e2a4ed99bd5fd3b7a0039481a1f8a9388f926f04951d0f4d0000bd0f0b3f3b428a0e5e721ad3b3a665601c36e955ea1ddff4190e6924811667def60b11e18a500fabe79a91dc61c5babdb83c7d13cce6c376b5004ed24a59ac300a091c50e225b06a272ae3aa21f1a7fff06c4fd4f36136ab0b48b03e2b1595080100000000000022ca408130d15b26b7326081a717d82420e44e9d8e3252c4237562c57892766c0a1793090100ea336e5c8b86755f64b118a8188168b82bbb8c4dea009afea08decdcc79004a7831dcd3eca12e7fdbfa76a63316340195313c2bcb0ba6eb0d9b3e503d05fd41121e26930105114bcdbf15ac30dccad486e816b2f8a03d7199da4df37b5ccf90d02010000000000684f20d86080a49f3d0c11a8e8c568699923d0709e021b2e0be92420005b49c6481f78130200266c415f4f556ab170de02ea7b811df77656ca4f22df69ab735d8a51240c0136e7e37f06e4952cb5090e8c5672e9a2144ba3c2e685d7cfc87d62b1b19243b406bb8ab926694645d62b7bd7f3d8e2b9dce6065babcd5e80663707dc165b5bec1e0100000000000022e980826d71518210b005f8a42573c9fc7d21d9a1ac126bf2a502224efc9f7d89a2bf0b0200d33f6e32eecd4d9f6659ce75c60b0b9ffc0fca15b3e96877d9220b823fb040e1a0ed72343fde798e248f53a1e794a926056f826478868cd2af0396da2374cb055ba205b3a9787e3848f9f94a8c8043ceac6b05e4969f2e6be31036396dfe9b0a0100000000000068bc80e34755b2dbbc63b36462fa116d43e923f042d36ac5b39e9ead3e9182d4324a131f02003a6cda86b561f88aaed6b5a5acd14106a388040615b5a5cfb73d55e4da966af077a49250cdddb2284d44ee3e10e06a0218c3190e938baf26a5663e376c76810d13a686866db795e13165dd2c9ff2ea2dd155fb95764170db89e3e6798c81550405020000000000265480cfc57512d6a61a66ae3d6f60cc5a1b6fdf394f2b2b54da0b89db53607f28f536dd00009716a66d76392226d9727adf82d0d27a81d7c959762ddaff352165e4dd15f85ac4eaaa93df588e17f503e12470a3ea4c018c3299fb0e56a485219cabca043877734962fd3abee830f7444ab64e32c742f052d0fb700032e1a4382ed9592d67190100 +23206780 +6 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000bcdba3af6e7a8dd0f3c97c88944bc80f814ce928505bb36c5a19b71b0abed50890fc37ec807d63ed7928160f4c4717cc9444a9c0c17f63742908823a27fe172983b4ec6a5c2ac9591f188afe46b0ae6693c6b47fe3af66e3a7ed8221edb25c0a01000300e9ddc7a936f6c7754edac07bfba2e173325682151992c706a89fc7cb96f9105118f712c1b4b1f2af1696f7f9710e56971451aa846f75ff66e3b4da0cfb134d352f1e51876777dc52e0119b8a235259ae5cdf184e6dae4b006f70680838d4dd2b0200 +8519680 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +030084b90b5e2665874785ba1c6869687eb216583d8b55c2f7adee95a04ce9af7080021a471495dcfeda34485c632558f624f00a0b61e5883a8d07a734fce6498301b131ef852ed82b0b3f67ed0cdca7e3fe9eae2d662df9e928ffa45c57272b210406000300e6874899ff7c40250dfbdd784edcba1dd84a7888be08c202bb0ad3cffc3ef4f91368cb3e009d99fcf7000227939a6c6bee07dcd9959bebb33157f77835e45938858cac24c31e48dcb1a2184e5b8d45128d5dd20609e46a659a794cac3e617e220100 +10649600 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +01006ac90bb74665e67539dc441610192390390167a2e81b921e16a075af5615f05c8d2ca9eebf6e16fa75bcc6c729349201961b1fa2c090d3ded8883efc3c3304334909c3a3b3ffc49cb6567912c94f6ffe55cbe6c589677b2609b253b9f7b8303d0200 +03005331ae7c533cee9aae54c975d8ebba721fd00ec422834187689731ea6f946484963f300517c32f934d80d92821eb3244228832a1169b34bda0b093f81b8c23070ea0eeb9b9be804b4010af311879e10fa928898809dd0439f17e13525b7176040c060300acac557b9a180d4f728a151b361254d434cbc588069918f61e45570f7868ac6ff431699c7d62b7e24ae1602bc8e4aecf5cbe8a984924f9d21cc87e3c0ec4f07795d13a29c85b8442774d7a0dad749e61186d833c6dfcba38afd06097958a0f0001000000000000064dacd7a177ac0e3660cc945215d8fc4e6687b3635fd43fa8cb0a7098c6ede2ec5f9e310300a4c1492116162ab54ed00da11c5c8ba666214e4da8082a23d27cd54655927f732ae38d39bb502da9f6194a64e08b4609f432c1ebd13d398e8701179385c2cb79e1c5db8f349fbccf31ae701647bb74b8c91abfcd018c4c0e3f943a01414471790100000000000012e904a067510d5b0d2deead24f085bb41c35d65115dda3d2cd2a53afb8566b768a39c2b03007412d12935368c1aa50996d111f86c3cc2e053948d2e1ade3e2fc6ef204b9b53d3fd820a771f31d9d32131a9ed35b59f8aba8556b6eafeab048ae1749e984612dbadf2de7a705c0eb2327545c33d1585e7ac86a1a0556cf52c7986c5d0af951c0200 +1859085 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200ef65e03715bd74720b7c6dec80fbb900731c0f1cbd996e8322189756161769bcbdae8c3ab30a312a2a685d7de16581e09d9ebeba3ec048f0ecbe58a0a317bf4c5a5f5c514caf6e9da43fab00de255a2deec3a76feb9bc0ffa7e433047e65d86f0100030013ed8a4f94a7ab86e551333624accc1227e9492a8b32bec4153af3771a0ea8f8292f78fa63e1ef6b33fa8def4c9b990abb79a3bfb0c06cea21622a457a7fbf808286c389ba53ad9b9aa05afb21ee7dec3ef57fed061c9124de4cb35f2571fb600100 +16833661 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200d6dc51c730d1110bb52c1b75f585c771785f59a54495855d1418a86dc73bb38c5278d4daf781a4cc7b3f964ccd683bc99c4d9a47ee6fd6cf910d1810cf7f511de1dd4e41a56b803c898903c7197d3a5e964a29fe35bebdc0fd0bfff8f9186529030200009965e9bf563d9eeff4cd579968a214e20b65a518449fd14253637940ed8d922386b542de65b3e5aaf0a12fcfe3dbf5050be40564d6cae22db8922dccd7b9e3196fce7a6ad131d618ff1f480198973b37badbd3f032a3286bcbfbca736c63f5220100 +17590251 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +02004fcb2cac3fd80aa6eb1368f4d885e2a4ab90cd3df2a4560050baad39928dcbd97351f6bcfd3554d010bd0cc34bd253e9f80d476e7ab7e01d65562f60bbd44163c13c89a0af82a65be26987c486a1e1e370f9eae569765689a70d0003eb6d167e0100 +030077c7d24c388a6d9527c6b38708268e91b124f16d50720ef1ea025341ceea2247b8457ee2a7ff77761d9d86bf2884ce74ed582103d1ebb50ac76d8f8edbc4212557e681cd2d9fbebdf3e26b95b43f44b2d3734c30cada82ff2cdf0646c69ace140100000046e8a6bc99d13c29845fa0832104736add962a32545ebb66d1c9da460529b82c4eeaa339745ca932aa319411f47bfdbf14fb5f65bafef34ef112a88f4b5b5b47cd1781ab2ba0b9542f0cf5b57cc557e9ad05669520f2f2dba318530c9fb2ee1d010000000000000290a4d2048ad0ebcb19649107ec5a3ea133ef95bf6c2495dc4227fd5a595e578c0f554f02006efa39acc4f91ea26761ad02e6e8409ce7cf6d3dd84bf78c252674697445d15c11b40a3b4b1b6fed74f4be566d33c4e9550a3882c9f7f4529ff69a635d8001578f46db790b6729f7d1f61138dbefea8e6d1e8543f01ab4637a2e0f46a674b87b0100000000000007b1ecdf84836b33db435b5f5193376d6b7ed95fef919d659626c699a00f4329842ee0d90000aa04628714c5405e8908016c86c86dcb3ca7c609be65a50f178b1ece5d34f898231d14c11a759e0344b4c74309e106b16bc8c56ea2c41d5b5748e500a021cc03c5d88a39438befb860d0b7ecf194656e73041d336c27eb1bff6976c8eb70a2060201 +756590 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +01006a7629005d27577b48f68c34191b54d2923303f391c77d22ad6aea6f1a34a8e3cb533d634a45c2b8949e9284e2d596546d1463a19ffc1cfb0400c41feb91bf33fbd9155364d3215b8ec1d85b1775875b4819a4ae66fb0660138177d166a7ad0601000200deb87c2a9ed2279a773366e720b502d8c3448c9933d09c467e6a5948fb3cd0b4416f2f078c036a56bbd54d09b77851aebaa5704e5dbe9cf5920dbb1e6e7dd22c11d4401acba978aef605e41f782af9ca157b589ac2e0347e65f94d293254ea080302 +20091381 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +030077c7d24c388a6d9527c6b38708268e91b124f16d50720ef1ea025341ceea2247b8457ee2a7ff77761d9d86bf2884ce74ed582103d1ebb50ac76d8f8edbc4212557e681cd2d9fbebdf3e26b95b43f44b2d3734c30cada82ff2cdf0646c69ace140100 +0200f6b255eb3453d7fd573c8993a48609c488eaea200381d4177b6a551064b73455825f3dc4ea3324ebf7afebdea2ec6d2f59c202559a510dc2a31d4ee7574d6c42edcf41083d8da9f3332749ea10eb77189e3cda7b4135412054740e269523a70901000200b276274fbfd04c0aecdc54f5ea8e91105e5dc7e1358907198042abeb928c9fa02573f0cd85c965b3714b42b45cf7f1fa570e5fd440457b2b9aa63df7ee147d2bcbcbe247556043c8f073ef47702a70758576df41659a18489f4f24b4d2b69d5901000000000000087b18c107547063f93fbc72d96486cd8eb4250fd227015ae3bc1d30bb32711c9654bf670100e6cb27173a88331fb9fca7ebe4623fa0f6b4ab135785c8911aa8092f38fa7fbe511fccd78fc56793660c9994f1779f4f3731dff9e9cb6567972d264bfc798f22379e39111dd308f56c392cd0b892049628b569005cb75e6c92e3ae1f1828750a01000000000000197148c104e664639180c1a58246538bbb8f36197ab2e72b7426e81b32a1b1d5940eb14301005ce3452dbed4d2b0f6db934622859229f45bafc8442f0e67fff463b8bdaea33bf01e78cc565240cc60744dc1babd5dcfb2a514222b72c06bc2cc1fa2f02af20d3dbb2c6d01e382481bc6dbb0c724aa27748c9b8639e7996f9d672d67ae38fd0b0200 +2501130 +2 +18d26c14e2924725ac4e11c7913217c558c3b4a034a3a97838cd835b9793480d +1024 +010040928931fa38500c735a6fa35f488cf60c96c3071c03abef23adda72909b424eeb1983de5842fd78a3c34e983a3fe629643c8a37583b5753267c9bd1df93b704e9e6ebda304b3529793526a2ecc57b904131d820b68f83b515ccdc7073ce3c140100 +02001e71bbc55cd6f524faa07bcc3770fee35ff1834cb1817194ba8b2e1a2fea376547a7fb8259f5909b4b6c85657c3561c26d1e70ce610fd0d4f5758dce2d639d37f710e17e0c0619caa9de861e71c0651123d3a295b2db62539030e2c082bad91701000200edb44c624a577b96ea97da529753d9e15de3f0c93226f4b9c7708193aaf432f79730f70338293c2a55c5b1ccd6eb169d72f4b9ead3ac5140f1592f8da95c5724b362a9bf74d0c3a14287559849db2905b86b88178da27290e8cf8a4faba7901f02000000000000516bc0ae1ed9b55df43e81af8105bd6d3d6aeda55b917b4240647951e0bba54ee17acbc30300f1dd2dc44aadac94bf48f3a9ab3bd4afbaf0fbe1544194d97a655e071a6d12f5715c55c780a974c7318c39823f059b62c76984050aaa2ad815100e5bec82d005e8aa0af3c4bc595f43d47e0dfb8f6886d118be635c5b2933d1788ad8522b0b0304030000000000f452e08bb5705ca3de50e4977c05af7b166cac4ad1f2a15c4315a4c730222f46b47f1c71020013cec7dbc341bd62d4e25d9c7ee8c68ca8ea27b750fc771f3e2f56d53fd09caf71669d98eb1d1413c7a2643fedee45705a1e49600832db9626c426817d705317794b6d44a5e70a9699d0aa18e7310dc1abe39d4d8375ad206ca6c89fddbac01a0201 +23206780 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0200f6b255eb3453d7fd573c8993a48609c488eaea200381d4177b6a551064b73455825f3dc4ea3324ebf7afebdea2ec6d2f59c202559a510dc2a31d4ee7574d6c42edcf41083d8da9f3332749ea10eb77189e3cda7b4135412054740e269523a7090100 +02003482c6eb22ad59080247152599114686def788d841ace549abbcb2b46a4be1fec52fb8ed91f3d553df5c82441ce5943468bbea6161656e2d3fd92fe11d11453d171524d0e6e7455849a6e5e8dbd2e65e1bf12517c4ae31ab833a664647e7b230010001002e098ea83743f347daead7bab876857c1e575be680d25c7378b46f09de0b965637c4dbe629307d4a87550ff55900f27ca51ab22144941af0985e8f7ad08f42098f5a64e34faea90bd5dc5d7cb2db7eed60d78a09cd0cb780f88c8e47efe0a112020100000000000448e0c0410f13033223c0dd0154e477749752008e84f27ee2932a7e1f05b5761c75e8650000ed3c7985e2eee57cf4c549fe935057fa951bbd6adf06e8f3107d3840fa6f96c9a461d9574c3cd011b761cdde2befd201904f72a3f3272749d909d05de2a3f70c3a0e3b4f0402940c56d3671ad209945263c3cdc6b91beb1a5012a4584d86340a050400000000000cda3ca02bae25d2aff2ce41157085365e2158bc93000a9b6c4a5358b0ca9482564028d70100f43d1db6f24329032c6a2e01668e76b6a6fa14852ed72991443a4a7396a46029c4c1bc415923081c9885e6615c45924e8cc2549f4a9a063a392f7a575d394d38bbd12095e81e162b541477b7ff2b5b47b53db7cc11f2de666fd9b6d39e751b050100 +1263558 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +02003482c6eb22ad59080247152599114686def788d841ace549abbcb2b46a4be1fec52fb8ed91f3d553df5c82441ce5943468bbea6161656e2d3fd92fe11d11453d171524d0e6e7455849a6e5e8dbd2e65e1bf12517c4ae31ab833a664647e7b2300100 +01005734c00f28a4dade4677289964f8a9aa507bd1fcf9acb2057113afd2bfe9c13372ddf73a53478ee20dd1411ee9f5e299c2a5d0f9cd258862d2fb1e3f7cd46c3445e68b901938d7d628794878356dc5e81b98acef154dd6f9d7e710e1905275110200010071193ec5aa281af2ff9377b16e61ddbeeaa5785a255e1db8e5736ced10e7cd0e2f58224ef126c375df94dcc53f3155f7454bd526a057aae569b47798a6dc9758b9661b596af8ce19d7cbaf720e129a7d3bf6096b5968bc87cadd83e814a739780100000000000007794886f17f234187bff0e01e15536dd2cad82cbb079dd631175f677a3c0f1be7cfdde7010061de6419e3216fe4351a7ef04ea609c9e52b38351567e55d52823887515678d77ed2204f5560fd5adfe2b45bbe428357bd061ccba38d8698c93b8b257cab6a34c9efca64e3246b244d943e287257cb05d7d950c9c1e383af7e584dbcab94730402000000000000166bd897df72baa845a7e436ec8c7070601544dcd409eb33f77556dc67fd6c420a69d50b0100d9b4078ce7686b00cbfb9f0c4339fe7b5df06a1375513b6494d9c1e9776568a25d3575814fc297445eca06dd7e3fbe34a79d85517255052bf0a7f86c727bc25c9ccc10953d6b87290b2b4321d7c61c430b61a08638e20940b174826698e1d6800100 +2204158 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0300f4b0c64a4c610a43119564f420aad1a9288ae98af27e25cb6ffbd6aaca01a13b88d3315cfd77f97ba19599fc506eacb052ec7117f70ba0dd97f2f74859a08d0bf36e399504f6deeba2954e899d84b3ba06a82d380f24f168b9665b2e1b7c0e09050003001da34294073e2f7806e278cd211979f31522d29b85b2e931193596537e3016a45e9b1c1c1ed85c1772546a181a5b5097f13b48199c944222bbd4a544a85d9c179c0fb562160b7d7ab2def22ca63498b55ff8a5d0bcf0dc1121379369e9eb47290201 +12779520 +0 +f8212c33a65507b90a7b7b11ae00ad97d6675ec38e3a80659179cd7328ba51cd +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0100cc66e3f7ad65ff578c63f9d4c867acfa74de6714652a75873da8785178cf94262f991116928564da832f06f764fbbaf40f2c0c66e6d9d4c3d723fd6abc2a13701d53b203605f9ba15aac79d3142395652550902211179c98524db5f533b0ac4201000000f3b7957faa436c85e8489ce5189fd0a3fc7614e2b69ff960d0410e293a16338a1b75b18a3b115f8406732f4401ac8f6498e54a54951d17a9ae95b6cf9d817c00086fc2834f3644876c077ba436ae7154c139e49b32f0a60de8f1da2b6bddc0080100000000000022ca408130d15b26b7326081a717d82420e44e9d8e3252c4237562c57892766c0a1793090100ea336e5c8b86755f64b118a8188168b82bbb8c4dea009afea08decdcc79004a7831dcd3eca12e7fdbfa76a63316340195313c2bcb0ba6eb0d9b3e503d05fd41121e26930105114bcdbf15ac30dccad486e816b2f8a03d7199da4df37b5ccf90d02010000000000684f20d86080a49f3d0c11a8e8c568699923d0709e021b2e0be92420005b49c6481f78130200266c415f4f556ab170de02ea7b811df77656ca4f22df69ab735d8a51240c0136e7e37f06e4952cb5090e8c5672e9a2144ba3c2e685d7cfc87d62b1b19243b406bb8ab926694645d62b7bd7f3d8e2b9dce6065babcd5e80663707dc165b5bec1e0100000000000022e980826d71518210b005f8a42573c9fc7d21d9a1ac126bf2a502224efc9f7d89a2bf0b0200d33f6e32eecd4d9f6659ce75c60b0b9ffc0fca15b3e96877d9220b823fb040e1a0ed72343fde798e248f53a1e794a926056f826478868cd2af0396da2374cb055ba205b3a9787e3848f9f94a8c8043ceac6b05e4969f2e6be31036396dfe9b0a0100000000000068bc80e34755b2dbbc63b36462fa116d43e923f042d36ac5b39e9ead3e9182d4324a131f02003a6cda86b561f88aaed6b5a5acd14106a388040615b5a5cfb73d55e4da966af077a49250cdddb2284d44ee3e10e06a0218c3190e938baf26a5663e376c76810d13a686866db795e13165dd2c9ff2ea2dd155fb95764170db89e3e6798c81550405020000000000265480cfc57512d6a61a66ae3d6f60cc5a1b6fdf394f2b2b54da0b89db53607f28f536dd00009716a66d76392226d9727adf82d0d27a81d7c959762ddaff352165e4dd15f85ac4eaaa93df588e17f503e12470a3ea4c018c3299fb0e56a485219cabca043877734962fd3abee830f7444ab64e32c742f052d0fb700032e1a4382ed9592d67190100 +21573510 +5 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200e7a4322faee06fc8b91474024bbfa4c545325769c6b378b84cd69fc248f7c5a08fc40c9dce91444f29c7d7bb91fefccb5d92c2394ba21ef5d5ba10f77d73e23755969aa2e2cc1965889eaf63af8d8ec951348bb0575e1f000d1c14a230ca041b020102006275b2d2bbcc7042b15fc6999cd9a02023cee10eddba9979836cbd99d26572aa8be1474ac6f79c3641269978e8cf04452b864e5af14d77575d9af618c6b25d159b1a7996496e1aef7c0e533b1a5e9acbbb47da46b30d3bbb36277fa272a0ee390100 +17039360 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0100a52d8081c8e5cff9dde9c53b826f2d7bd2975f96f64adacb6916676b0735aef4faf805c6f6d1a1183beab4273026927c06fb8f1ae57912068f43e904353c1c39efff4b412fdf4511b1717f73ac2bfb0d334da1534ad28a92f5d16e8441150646010000002446538a3f0e9997b96a705c7fdefc352ca7e4d21f0ce5aceae118788de16907e2a8fe97ad378ed2722a1166b62fc116c4826aa1b44717950aee86a9a806296c990a8b0de19b6d182a23b73df042ccb3766e8e900378944898acd3ca540cc47e0100 +14909440 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200e7a4322faee06fc8b91474024bbfa4c545325769c6b378b84cd69fc248f7c5a08fc40c9dce91444f29c7d7bb91fefccb5d92c2394ba21ef5d5ba10f77d73e23755969aa2e2cc1965889eaf63af8d8ec951348bb0575e1f000d1c14a230ca041b020102006275b2d2bbcc7042b15fc6999cd9a02023cee10eddba9979836cbd99d26572aa8be1474ac6f79c3641269978e8cf04452b864e5af14d77575d9af618c6b25d159b1a7996496e1aef7c0e533b1a5e9acbbb47da46b30d3bbb36277fa272a0ee390100 +17039360 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200585839ca5e371ca95079e155cb8f1d123612ba7edc30054326dd0e90d63369f168c0541500d25e21b9a354cb09559f56fc71f78d7d361488d7f435a000578a0301d255bf5473e944465a118bda95d3269f7c4eb1f60c1c6b6070188b35ce0506050300004ef6305ac3fceedbbcca6fa2060a05d20d9fec538a27a1f1ad4e075178ff7a9429bc62cbca94948be299ad4400b9c956d64fc55d902bd8b62cc2a00075de9b67030481104c50f30c0e75cf3f43db299800e0f44b69e3530e0b1d6bfe968b86620100 +24725858 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +01006a7629005d27577b48f68c34191b54d2923303f391c77d22ad6aea6f1a34a8e3cb533d634a45c2b8949e9284e2d596546d1463a19ffc1cfb0400c41feb91bf33fbd9155364d3215b8ec1d85b1775875b4819a4ae66fb0660138177d166a7ad060100 +03009b9d257d4308b14fd46e2bdbd5c899ccc3dce4ed8ec3bf08bcaa7880a789119228ead0c252118731005d2a7da245eff8dc10ad3b78472bdfb17c4f4c6934c7531c0cb06a81cc34bbb2bb0ff0f5ff6d2c61cec782311e99ca4ff6c82e9597a73701000000e7f12fbcc00d1e4104a921a2417e47c3496c14e22df58f6ab8873280e8ad17ef5caf70e6c9f5cf804f0efaebef52196c3dfd9ea9ed74be81ddd4b8b6b87bb010ffdce68256f82cd9982a88a9f05b8a8ea12e661a792391ca39d8570ab0db1d0b020100000000000448e0a088bc2ec8571e34f3f45d741ae2f6b699ebaac07d314a948b098633b3278287790300c900e4c75ed3a5cd4bf69d9bf5d098b51a348410ff3290f6a51e1a3338799a47589eb026adfe2575191f4ad49927f4cba01194acbfdc06960fb7c62955a74e3f039a1e4eea2ffabc5fbd23a9e26ab67fdd2a461f25aa0c279f14dfce5460422c010000000000000cda3ce605a40a8e63c164b77d181a1ff49f566dec1eb0a8ea8c88da04292561788e38e5000049e3a949706862e4ac326a4f35450903d34bf1ee5740f34ea2c0b13b529bed57562f9c6e462b787b564da375d191682a47e84d2d09d846cdc2984e9a957d6c1df5af491c3e844cc776d7145fe681da8f831c7d6a5da198feb87766ae4fc0de090301 +1263558 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03003d4404805cbdad9f1d171bc9be3e7ab802ec7890ca9f5410c98ec368abf9cc1367bd6fdd4ef5c0583952d32e47270642470ff68387b2f29d1f24c71dd14c8c808b4954fb94756d766faff165009ca5ba6d062e8ceb6e44012cb9140841fc656101000200232a0bd275b48dfcabc608c3674b33f7562ae1c3a92df76bbde13985c52f532cb336ea3f07e3fe8723e34202ac88170a3ce446b242dad7cc276976fc13680f20237a1c62eeb861a5bd5fa7ea70018a4fbfe29ae79d5aea86f2c5a468fc67710a0100 +23559097 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0200271f995146a778dc8d1d233ad78b1f93faecc29bcb4ca443062a4ff603e0e4a31c0b41fd5ed33ba8fb7e85261f7786cab7005ec2d773cd417553558371e0b42d3bcfc80716799ef78979c90ae50b1945686253ba2356b691d591a971597a3b350200 +03006c10d24ff94ce84707ed050142321d903e0d783b84f7e280b1904c1f4bdf4d4dbbb16a389ba01ccf0cc78fd66b7d2f55e352437f30b6956abd6bd10c79793e00833ac4c156e2266b0f478655e16ddfcebc696f5007118d1c3844e45426a3440301000100894079520cefa4baa469b9d8f484ee6a2cc1f6aa5e86c31a13106b9cc66e7773fe11d84eebb6b345488a0bbac4fa04c59d95216d4d955027be59fc697e46e4535d07715e976e72046328e2039e69804b138f271e3e5d69c88a83fe459fa4ac0601000000000000043bfcf46620db951cffbd826e755fbce3ee4916e77481b6239de59c02fc3e492c517bb30000139f583e1d4b0a92c0c6be420a7eea17c1b8768a2d276b65a37107aef8a6772e3b2919b5aa4c88e3640cf03bf3a25c420c5f7d6942b473688f9b1de470a74210535f53d9e97b8d59396c8b5c4152e0d1d5a74eef557a654e7fab66f311260106020000000000000cb4589ec227cb1b76987faa47691394f3024f5dacb1c4be0f647d7d7645fc29aab5ad2f010057208897ca9ebc08615df94475a9b14b22ff170c5b21f33d10cdf46204817956e7889c6644ad984c4275aa51ab8ad0d7798349757b87543fda6a45ffb561254b31fe7664798c3e3f9cc97780299dc07073a2e9b4db127b7bd40a8e816d865c110100 +1248945 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +01005734c00f28a4dade4677289964f8a9aa507bd1fcf9acb2057113afd2bfe9c13372ddf73a53478ee20dd1411ee9f5e299c2a5d0f9cd258862d2fb1e3f7cd46c3445e68b901938d7d628794878356dc5e81b98acef154dd6f9d7e710e1905275110200 +0200271f995146a778dc8d1d233ad78b1f93faecc29bcb4ca443062a4ff603e0e4a31c0b41fd5ed33ba8fb7e85261f7786cab7005ec2d773cd417553558371e0b42d3bcfc80716799ef78979c90ae50b1945686253ba2356b691d591a971597a3b35020002006429e045b388222cbc064bd8f1a723966092fef577c2f357af415287611ae92fb1d3b32a6fb3b8118cd8e963f58cd484cd446e4a123b25643d4538cc2e8a5a04e5d6a258ddfcbe8e28c0e8e93500640b8ffc30bc6ddb544711ec06bcc42d4b051303000000000003f4e4d8e3be1a895cb01d0b2ab4f07bf982d54cdc869ef4124a681155d68dcb2c004a69010066c1ca8e219dd033a0eaf89000fc6482bb4315042e514ba71b3eb95af999a9f9c2bb8f68b1230dc177a818ec31d41add1aae5b134a97950db21c487e116f9f478dfb9b1a7c6c469cf2bb7e2131299897024ac9c68a6808322222914726fbbd7a010000000000000bde48b519a9bd8b6615e79448c305cb3be7bf38aa21c18b0e4805ae84b0d9582209541900003224102eb461da1afcb905c84f41620c004338a12425516583a30edb511611dcf2253443a624d12b42f161c24cb712dbe46150c2357a62b2cd6597cbd03345498b7e739a5bdb617bd9618a918fb6c6e2f4428af727e25a69346236860108b75c0100 +1166761 +2 +f2b06664b940c99631c799e30d8fdedc6155e0f7bed8687f1d635d67a091ade2 +1024 +0200aad17d7935d43588aa9dcb7c4e2b6822cac040e7990de69397074aef57dd0afb2871cb602ebc9a3db01c4978cea61ad758405fdc14a3e18538d1874cd519166b013a682f4eda8b3ac47b1dbcc340ef8caa72042dbbaaa5559a378bb3b4f0469a0100 +0300c0e14156162096915d1f4cc9b336b97121869b34482bcd81e836f55a51f319aed055cc9c56d69a57f4157620e644aec1483d34d332cb6d043f5b070f02ac4e18df792c092ce4e91c11c4cb86dcd90227e1da799b7a030deaefb064b0fc526e16020003008a490a8f2629b74f8ce705dbc8595bcae0235dccc1ab6778409e286e2e8a7ea88d852e3a789fb341e08c2d5993860fcc5bffea530b952f27974da2a6f1f00b5341c04710f8bca8bc0d1962102f5d20439cea02d0d8538731ad53f65d1d25d6380100000000000022ca4083bffa3b34afb6152e9c1598d756a3543d433ff8c42c55f21ebcd9311c75e9d8af02002f3cc24f4ba78af139855de0dc3616f91d8e33a28fd58c9924f8f657f52f7f7c2c34a08d0b0b07543fe002f8f68449ac7ec769de512bae3a74a3617e6f208110106f0274b36eabd20a0284d88cb73942c9273c2c9e80a9654115cd93b8bb590101000000000000684f20a43b7ad02068b489bad0848efbca476bf849478e7e5b378e9cf047981b1206380f020098bb715d336321ed0dac33b1ab56376bedbc66b8013d6126544ae2f744f455c51c1c8d2beec44cfd4a54d50bec87279a62dea13fbcd054655d78dd2d6e165e713f17441d3e5e0319a0944b16db2ee0edc42fa3bc10d3964a241ebfd1681bf96b0100000000000022e980d3075721b1297cb518b35d4cd55b4fda8324ddc972caeb3e57bf1f4347e6ab47e70100f3b940b170ebe374bd3a1452baef43913ecbabc82553bbd222a346a6c8ccd46addde6cbbe0b0ece812f982c42ff2316769bfc742cd2eff0543c8aa22f1a41e10f0db5dae5bb96ba0037f2e63755e23c96f27bb0cb61b96305170ff4116664f010100000000000068bc80b494008f5758899fd016e2bb949ca368a1650b0ac0c9938797207202e7b93e40b50100f4656745ceb6d1df3077d19b31e2b3e23bb5baa9d2e09aa4705f665d5e0e079fde26f3f4dc2740453672c362c25812427a76e8e2a2f6a874df0e75e77a56902d496f496a1a9066bdd39388efa8980c18fca826daec87ba4f28cb80f17d3b433f01000000000000265480cc51566cd64ac9da40b27dd6614bf395064fec6239ce010d18d0f4891083f44607020093495b39fa1d8a1b8dd9b3a255d0cbaa9f32f371bd5820d125e4aef22944f2457a8858ecdb5025de9e08c86fa4dd51f8e988cb18cb4b8d0cf1ed3fe5db9afc377be0f721f1cc350bc88199510c961cc44c613c7018b6d649874f58d2fd2fd9430100 +21573510 +5 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000688ea41d7d1586f1c6803bccf7b121f15573db595d6f09273b24b51f5677b1e05e9c8c30f8f043798dfac5d9572b7956f61f9c5364227c35a4d1caeebf3f2269cb983eafe6035a30ded9c5756c0f6d076e7a487cf8613ce276deac6dd4d8372c01000100b5ac52e782f91c2dfdee35354ce14041618554b1b4f7b3ab09ba2eca48da5250ce51c49bf60430d3c072c295b7ef2275c568c2d3f869b65c06be56be4beb78202b457a217474af9dc9b9f782bfd5e95eb93e66d4760b28be0e5744096618c7440100 +25974803 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +03006c10d24ff94ce84707ed050142321d903e0d783b84f7e280b1904c1f4bdf4d4dbbb16a389ba01ccf0cc78fd66b7d2f55e352437f30b6956abd6bd10c79793e00833ac4c156e2266b0f478655e16ddfcebc696f5007118d1c3844e45426a344030100 +01000ae45761e7465f6d589fda6369f948bfaf1ef8158eabe977f9231ad1937b4cb504f04fe0b4bd346224ccd48b00997643a89051b242b6ebb284c497d31c68a41f4b65d6167c890ae9eb6241e26d174a2f46af7f276925ffd07b7c1398aef19d1901000200a46cd1816650373be191dfc94ee8b6db87e11767251f4f6b0c63a8f971655960ce2dd2784b144a212936041b3a81c04b4604de7d2944f4de9db1d94432a77321df17c66fd541686fde967cb917c92423d57606a932b05a67cccf33d4bafae2200100000000000004f524c6277a7c986e3de56360ba2a59083d889cfcf33336e68778aa0f5d1963badebbb5000061515fbce958278a0ee016a166897e79b111bccf7d7ec1a1f9902cfee44a718119154ba2930ee18dbb34def318586d426c5505177e3e5cb5a454d4aab005b8565116c7aff6861f4517bb9f0b60a6402cb9e1ddf3c4efe6c7460820d564c9c66e010000000000000edf08b8211229eda8d2177b349288299054247eddb8af18b89be19b40b87a6eb0f8bafb00002002783143490c571c679a1feb70e2796a0e32507cb1962b1911b5fba00edfcd4458199aeca52bf9592228746d90597c8630fcacc75514f3feae223f2e7e4d131959df0ee31eabe5e99febc5d6567f2466fbb62d767b17b9f8e8ba88f3c9b5170100 +1461985 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200741aff23c492b498d3f61cb6185508e5391f0d215cf345a7b3854b4cc803b22ac2b81b4003d3d8bcaee32ef2520b1b0ac622179a0aeac24277cdf0b6624bdc0bc590eb93dc89dd33d2a0443815c6a335ca68c2228ae1fbb673b90322db3f050903010000dfb868fe83210f567a7722037315aecc1ce225f8fea8fe8c3b1381cb7bbdcc3b4818f5565aeebd68848d7adba7e48e45460436026ab9e44bbb2957b75f08fe083c983da22d94a7ae90635aa4a43d4cdfeebe27fedba6fc11f0cb010223ffb3000100 +19169280 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +01000ae45761e7465f6d589fda6369f948bfaf1ef8158eabe977f9231ad1937b4cb504f04fe0b4bd346224ccd48b00997643a89051b242b6ebb284c497d31c68a41f4b65d6167c890ae9eb6241e26d174a2f46af7f276925ffd07b7c1398aef19d190100 +0100db4b85069ada915540fea52ae7ba7353e652e69f06b3fe1da261ad8dabb47bc41d8c2d3fd265ba950d049033914b48d97a6662ef885eec36b999033360922553d22a50bfa8a2c2ba3592e8832591ba3843f06fc28a6c1ccb9db54ab0315e7541010002005dd206c31dc4c3724c5e112b60a662bb1cf47ad0a96c750999470edee217a1aaa7eda10e2b67dd46f38b964269037f09c49f91ad2db3f4b40b85a8b3ac464a03d815ec9beb41e4aa215a2f0673e58c3b12cee9b0cfa1cac050a93a6291de3e030201000000000000972c9b0d569e43d43bfe49c46136b4d5fc81eafe335bf3d811004bfe33b0e08c4bbe4f00002e89633ae0037d3875ab39dc7745978d08d8ea8f85dfe944e025a9d620a4e34452bad3cfbb54283744ce5b61cd46ef8b916855a543ee857c12ca64cab0e63d13d1418e5574a2a6bc84f0a48dd290e1558ffc61e99243b39d88adc879fdf9402f0100000000000001c4bca22ecb82a48f299728b5be1d47e1c1ee28a4e0da6aeaa8f91ee2b5437c48731115030080fb5aed95937e0c821ebb613eb740f778879f31a57c62931c1e651be977ba0128d32654a71c4738a5d60312a78b0ba45bbf31b1195f0842ddc440461e7d6a52f72a54dee27cf287fdbb3a403f8c81d0364da87133211fc06a0951177165e03e0100 +173970 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03009bdf4af793acbd0407e9567d93176f27d0a438f50c7a11404bb02ede24a9f08a163c9f086c8af05f200fa4ccc4877604bb2572859f8155ab9a234cc3056edb30ed405231250b71216a7dfa34c48cfa5564a5bcebb69539a9de4ba6fa22e3b400010001009b62c4b74c729636cfaca5e0e8e048c21aa4776f7f9e2310f2524525fd08f1e74a305045f280718b932a5c81b96dd21ddb15ab8159506aae7e2eea149940d6245723106a59cb97002d5f6290bf530ee17e8957d65cad5bad277e680c402b2f270100 +21299200 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200741aff23c492b498d3f61cb6185508e5391f0d215cf345a7b3854b4cc803b22ac2b81b4003d3d8bcaee32ef2520b1b0ac622179a0aeac24277cdf0b6624bdc0bc590eb93dc89dd33d2a0443815c6a335ca68c2228ae1fbb673b90322db3f050903010000dfb868fe83210f567a7722037315aecc1ce225f8fea8fe8c3b1381cb7bbdcc3b4818f5565aeebd68848d7adba7e48e45460436026ab9e44bbb2957b75f08fe083c983da22d94a7ae90635aa4a43d4cdfeebe27fedba6fc11f0cb010223ffb3000100 +19169280 +0 +f2b06664b940c99631c799e30d8fdedc6155e0f7bed8687f1d635d67a091ade2 +1024 +01000604cf6c438eb79c48351026bb78e0398856dcdd1a1ccfd136247d1c459926dd04deb3fb828b08be3a056cd75522e1236b1ba19ea411bc6b7c86bd69ea624b1fdf0ee440092dc4945635690042ef092fe707e921e146b1475b4344f6d01ad8200100 +000057b8a32f3a6b8386f995a2150ca3d825ae80de7418bc2f6a6244d0d03203f5fcf69a7ab190534b3d48902d398505d9102d2687171125a15711fc167b2349a004c4e765c963020f0a3ef7c86abf2dd75a78efe671010eddda726ed92ba830371401000000685fa969df6ab8e865371632bb66e4ac2ba5f62f611ed9b396b2ff7a26b3f7d050ddc505ea4b1bcef3f2dc8c684a18935af34c8123529326d3eb622efb908619693555817b797f27f2455ede013fdc25314346ec3cac5b7cbc2b7ae8c6c9e22c020100000000002cbd20f1364bfb1b028a5c6c7c74ec529a20fa9f46a2a99f8c9845f885c31a5cafaf30ad0000d7853b37e8798a87a458fadc7cee3f66657199fcd472bb88859c9243ecece984bdd8c2d59c01d851f7fd649ddf559174fc4558e36629943ebcb1a0bc622c85718bf15f3b2e16b8ca6915cc0c8e89617d890a5deaf8704cc6fde97ac7c69f3f170100000000000022baa0ae2b3efc4b36d37576bcfc036b7295647deba2a33fb28a6277961e3a36e6662a2501007bed7c4ee38dd3330b4bea06187474bc90e4f4c383aaa850125706c0a26d1133ee686625f4f300e2ebe5f24325ca35084301a36336e5c8e4d0dbbb168a171d58f25ed1a39f630ec26f490f3861875e08a27e9b7a7033320a2a9fe6bf78515d09010000000000006810a0ba03dd618a4f4bcc423b66937666967b51f5609269ec07c995b7df0de7513d6ed301000eb7d3882e74e355f8908c17d2e000dc8274a85285232161c789575962206af8a86191d11d2f0bab3464e0ed4420bdacfe969884d173d75a349c822526e476648d89fcef3d6de314d74b41e978ce62ac18aa10f3fe5f7583bab3655e29dd815301000000000000221e60fb691f4c26eceb5e3ded708b75c7416bcd86a3f0abf65deffbe06f32755e0522390000c80e97c3fef2e3d1394b2194210cdbf22ad152dd0333ce6560ca0085ed96f755bd5da2b79baa6c25056faca7b820b3fbbef7c7c17927c1d75925e60133412e4fb567c8024ab083e384ceaddd50b1caedb87b0f618d2090b224b0f6a70e276f1301000000000000662c40f4d232adb40921bf393781bf088576c9a536abb6ca8cd7ee48f89ebcbc51df4e790200736b799ffef3bf27de81e058b23a09128ca8fcc52fda19f4519ca334747b7478dce2460f2ad58ebea0c7fd45fba74e9bacb038d82fc211f6d0dc110c9d29012cf7af46c14c60ce65fd80039087e7da957c25671140de4fed7adad25a693c8816020100000000001a3ec0858c7238207a6be13aa5d56a5400b0ce3c0868bf81c03c4ee3fc7141f99c54b78f0000b0de01abead8ed861a55dd1bbfa0d11d3d8376d6f103d27775bb94532581d3cd12d28f6b0c0e4aa5706b65c2bed2ffd1ba3cdda9c076cd2dce573467c325cb1aabf456cb2dedbfa0eddaf2add3e4fb4b36a9a205dbdb39e9bd7f2deea9873b28010000000000004eaca0bc04f77a0f98ef35bf4473e639c3c37dc5e48b5cc84b27e7021c4f3063109685cd0000def3a042b695d6abcb5e1c62f66a990ae0eafefcedbf75ff97326e08fb361a1451f316aa42f70d78f62a47749905ca81025d1d54787deb66a69763aeb151c128016f747595e11733d52d265bc8a2a68d0648b4a4c49ef81a140e12ad9de8651e0100 +28817748 +7 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +01001973e3d92be4a2ad48a94dcb7ce09c262dc2afd5d825626b8c8c47f7ad68b2143525650d7e08b78e5d83598980639477cf0947f526c2a7517e7d1d0156e8fa653aa300b498810029b8c631e49d285128c2978eb2b5029f221e27d91561f00808010003003b0e33a30d2854e4e7fcc5a70e822b9fcf61115c1a0f2e6146f9455b002fc46ceb65c3ef6748ab37172a8c17287176b38ff355b6179d6490165b74ccaf6d812b199b2c993d2fec627f9d5875f281d2fdb9a21f1b96dc466d30bfa18d43d9ad530100 +28250333 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02006371f30c22f2e65e6550a935e9c0035ae123b35b17804fc281d376be43545ef0a9bd1f03df9e2f860bef4c0e4ccea4c8c8375329f6940e06051ef7567e2aa64d35d537648c85df22a3023e27f5006cad6bff5a268810e13ac43fa865f7735f2d01000000159f98613fb0f50f9bf22ed170dd92050ee81469ed565726b5b033b60ba69a1afa089cfd081cf88f3fd14dc07d2b506a1bdb3cbbd6dd9bf2b79eaf250efd911fe590e2ea74ee5fdb4a9e267ad53cb0459ca27bf162122bef71fda0a859adf6050100 +27436788 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200741aff23c492b498d3f61cb6185508e5391f0d215cf345a7b3854b4cc803b22ac2b81b4003d3d8bcaee32ef2520b1b0ac622179a0aeac24277cdf0b6624bdc0bc590eb93dc89dd33d2a0443815c6a335ca68c2228ae1fbb673b90322db3f050903010000dfb868fe83210f567a7722037315aecc1ce225f8fea8fe8c3b1381cb7bbdcc3b4818f5565aeebd68848d7adba7e48e45460436026ab9e44bbb2957b75f08fe083c983da22d94a7ae90635aa4a43d4cdfeebe27fedba6fc11f0cb010223ffb3000100 +19169280 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0100d2ff0c76512bcfaef2a641f61c745c9e0af6bdaf35b85a07be85e88f82a93376c35f36bacc888d9797a26d73c58ac4d15ba8855bb5d36787540dffff8765be0947dc99a4884207e0aa3cb4c704ac652469e1f744509b4819d0c3c8ce21ed621f0100 +0200008bbb7dcadc9e19d8fa90cd081f068e629f04706d23abe0f6eea7f02ab8676d30ba635e256887877a3d70d5495056afb81a522490a541c8e258c8807689e9319d6b2fa84024ae14739b86df768f88811d9fe07ef4d9a3bc10f14d6bb2ff0731010002005ab25b98db9ee7619b4088575d254dfb0635f6919ca5a5dc478e8a679fee35f2ec4173e35eab787e5ba8c434a376286380b0bb885d89606c1cbf36fa7b7d7961c9fa035c503c7d9ef3d2f9d39621b1445ae9fa4ce13843bfe94799a37b65fe50010000000000002ec50cbeb04f1af3821b761a3a3c4b1c0c8e2191d29e5f58c6cb95e4fd9fef3b160224ab0200291a18f937003c5ac94a82b24db1d9e9bde0e8b17eac30c57c202158de37a0921ebe17bdd3a7697ac55e78d9ff6ca96c81655aa60535ffaacc7a8d4f537a795d1c7d3d08fa6816ee46ee312b15f869e3139b6b63f1d966e5fbe83bd9f9037f08010000000000008c4f24a771ae4d1231dcdd31ba9f80942ee350c959f27f8e34703d3fb1d178414251b2770100292b5f386b585fcf605f1a06a524093c76807969e403fd79ad76c80318936059503256d03d6044975ed3b9afedca8264609656fc321650f947730a3e4a5e8d2815e9a8c5b2facda35b2e42feba515fe1c14421e0c3b5a4c75c378d76586310310100 +13792999 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000b33b76c290883aa9d377dec66fdf468710f4cbd3903b87c1cdb7ed414d9423bdbaa82ddf5519cb98582db8fc04ad7447d8c68aa711d7d7be8c04b3e7db3aa22006e8644ad78b7816e5d97e4c7a7b2097fc3f7264b39176faadecbc31c886791c02000200f74aa00966cd53d418e13ac421f28b66d437d91e639853c483b1f855d8f450e6f94e17c6b8bd0227dc10de9457472cd6cc207a8e68230dcd8ca589ffdcdb2972bda69ab88fef9b011f15939dffe7a6f30b34648db116748f4f47de929740ea3f0100 +42043332 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000d950f42a0af87162ec749f1f86e19f94fbd8736d04eac845ce6cab4004cd8f1767b30fc31e487525f9b3fdd79d9c070ab2d4e769d31362095fc741382d19474df75c21f0b1ad38d3ec235bcd41f00c4594fb59a4b369e11a82169fc918fd901f010003001ce33591de04e0816b3661f360dc551c2557d591797b00fec4933d3e0b0d199fe1e7164db3a4a102376708b950fd06ed586c94351c612819bc94ae3d6cbc9c19edb39a54c04af52617bda0c3252fe46df43e34bc7c65fc020e5cd075074d46380100 +27610758 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0200008bbb7dcadc9e19d8fa90cd081f068e629f04706d23abe0f6eea7f02ab8676d30ba635e256887877a3d70d5495056afb81a522490a541c8e258c8807689e9319d6b2fa84024ae14739b86df768f88811d9fe07ef4d9a3bc10f14d6bb2ff07310100 +030064eef03f0fb8d21cb3b16fc796464bd27f92caf4d986e735b3d00261571dd24a49a07acb0bbdad414b4109aa820e87f389db6d061f3a3d937fa0f18d4a480c12f7785cb3328420cc9ac331cb086261c4098b8f23c9fa03fa03ff3da545281b050100020037c52fa7021fad59b309c2a597f1b6ad45206c29899d0ff95090b2a7b8c7f5f1acbb595337427b44091668e109cc3b3f04baacc975378ba5e430b165ab2f614596296d32205364570e8de895dc42ce2cb8602913ec5725dbcd589658dbaf3e3d010000000000000f0b90a05b7a2e166f10c55eba96f146bacded22528af851a5a36203ec6456c06eea099102002ec5889049492a5ad2e090ca83b0b1ca972a8db148a46185159b9148342d64078d4da42a8e9c6ce2f905fd8348a5defed98d31f3f2b66bd29a3cb019800b7a6b654d666879d758f216e65d61aca15f30d4607fcd33f8cd9471b303403a381376010000000000002d224cf1e3f5564aee0e2af8650c08e6905fdf8a01a2c63877f4c57d2a86f07569753a11000066dc5e92178146ce13585845d3fa164dbac9bb1c2294de15ce7f65ca53595fed56c6817e8f78346d44aab6c92c7cffc8e14f95d0a31351943a120b469e41c10e8f9da552f006cae947a0db7dff793988adfe6e54e73f1d5e5231dc3499bdd6180200 +4436997 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0100db4b85069ada915540fea52ae7ba7353e652e69f06b3fe1da261ad8dabb47bc41d8c2d3fd265ba950d049033914b48d97a6662ef885eec36b999033360922553d22a50bfa8a2c2ba3592e8832591ba3843f06fc28a6c1ccb9db54ab0315e75410100 +0100d2ff0c76512bcfaef2a641f61c745c9e0af6bdaf35b85a07be85e88f82a93376c35f36bacc888d9797a26d73c58ac4d15ba8855bb5d36787540dffff8765be0947dc99a4884207e0aa3cb4c704ac652469e1f744509b4819d0c3c8ce21ed621f01000300aaf4ba37964114c24ed701906d0f2972f6e3194f65e7e97b06e19149cc72c4ceebd3571a287a6101a009c77b0243ddd57fd1200a3207361e4a3e67fa9b73db3d1b45dc82354d4f6cda1690530cc6b199693b6959d6c8741e8e46d5e1401a6a4601000000000000022b14bee1f2f2effb9b8e3a9e1f3f362293c05a541d13ca5648a61cc852bc3d19f158a90200a552a2ebcbe02a96e79f3f614f916d36283b9ae755c09c21b40ebc4c2e4cef0321a5f697f1fcfaf1a13558320020592c2294a912ca1bcb808097e5b86d1f8a0658d79facacbfe282a446d1e6b1bf7d284680a47fca6bcbc6002c2ca47a92a9060502000000000006813cc8112439806ed4c595dd4b3733ee18a480bcb8365df5bd94fd57c7b9c1cbd2d413030003851ff03e6b56bed8080b68ff253eb875bc71a8fd0da03057db50b80f45797a2b0766246d6934ba1a1baa9c9ce554a53b373562c797ff510034f3d474a800217a4946a8b00fec7e0ad56585c50e892f8b006f365e912fd81ca1421ba2fb492d0200 +639575 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +01001de21fb73e555f01ac7e3fe2a42fa9fc2f790f481f25e33a65df96531e3cd949917646c855f91d40f03c0c36aba2dc2acf9a426491c1529a65c37c4c854cde1963d846936d32a08ff7cfc7d8a4797d7d0161667c359c42f9ff1836a06d1e4c050201010044302a7dec16395c0849f17c98b555b41137211dcb33fd1941b9d747ca06b3a449b070c32fb5d909e145e191282953afb9ba94a495ef4dd3add9e772c84704403f7a6631f9d4183d009c3aba61f2334e918083112723e1c44c268a712d6384600100 +34078720 +0 +9dcec6bf2e37cdb3f1ac0849bc846e1d432dee470694639a7710b362de6ed71e +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +03002427dab46720afed7fa5d19e8f01a19437916cc496137b1fb4313d416442035ad2b4d29f4559cdb9cdef2a3d5271d22530f6d199f303adf0e2fc2a92c2e41035ef4b8724a66e0016bd332d224b0e44147d4ca9c95be9ba7605c573f6264a86250100000021cc5780b41fee03837771f92b22122a07e2dae5c161e19add0bfb8dd0d66ab63e074a96886301165d752edecdae9e76362ead1757e1cf74f6add8e07dc7906facd7c0a6720185119c256a94fa40b819ad139b16d22bf393e61c8cf78d401159010000000000002cbd20de6229e9826008bdf74c0a3f1257c35f7382337b2a0529cb6a94dd10aff5f2072300004b7d099631a48a3855d56df9d3ecb6547d3f48f16f1a87a8dbca746e01bb67c31d012dc6b496aa2a2555e39c7ff652d740b1e2fdeb200a090e219f630db311137bbf3683cfa026c3b71c31fd5a28321ce24c01443cdda2b7307184993d43cd170100000000000022baa0c82051bf68969f157116d61f5994e0a3b89b21bc0e088a23a9871f2259b78ab1bf0300a282ba7e89b5e28b9ffe6b660a20250b338eb976c9bb1be5c567d662019903f5a5c56dde6ce03a25b7e42afd7bf7fd34edbdefdd91d7a401b0bcb74c72293102314e5a2bd7cdb5c11646ff62f24959c87de30f397640d8d5625faadd0e15f304070200000000006810a081adf84709ab79fd549b41b4dee17c2a86ad8d403786453dda1d5302fd875c22fd02008b9e199ccbb153e92a48efdfeed6dab26ce2256517517861a1bbbcd5ab93eaae24818f9eb2148f483225a4bfa337379e6c80b454ad5905e6cee8e29dda1860052a788679df90649ba93d6b901997d740a883b91eb96c6ef5cbc9402cd376d7010d040000000000221e609f13953d2b9a4521c3fc33c93a08b4955b7ba56a8d8ec388e7bd29270505ab2f09030036726d7a223296dc312191928c4d0544aeff978e47ca4cdfc9d93233f26abc940877d348ab956a16591510c9d0c2e2be7748067f4429e165fdc6f251d5f58e0015a8d5fc3c636f84d38e3fcc10e1ec0584e6952e54c50dd7b29205789332790301000000000000662c40f1e8701eefae7e4aed1b47fc25c4bb2576f53c0fa2894d3068f711ea78b44ad8e90100bb70b59940f874360e010fee37f1470f3055532352de8a228bfdf8b0a035528736f0178f6203545e9b87133ef2d46ad883aae172f3da5dd94de6724974d3ab006f5948d74578dc93a41b95ae1d1ed0b88f3d573ec00a7d5b10df0e6953d16d06020100000000001a3ec0e47deccfed9d0d5f003167a109b6b4639557f79a6cd1d3a96baabeb6254f71110b02002902b49735afe42381220818914bc705c0915b99893a70222006f19256d0b9f69f510df8b14a0dbae2ab777808fff1baa36cfe23d419296d4b279f52808c2b199046f28b9382a7e1e19f2a6d8b2ae04826c3522aa384b02a51d26b69e0de3134010000000000004eaca0b452a287bd4a1a0c2163c50fb608566166889b24d847460fd9e217e0cda570896b0100b68a85769a971d8d2802e2d30b6bc80a7c5b60582e23f04679cc61d9033a0a69f9aa5179485d0abc1eed21afc1be1182807bf2e81be811b8129ba52d6a183b3427976871d412bdc2af70ea6c33531f9c384d4bff2085b2c620a0753035483c150100 +28817748 +7 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +030064eef03f0fb8d21cb3b16fc796464bd27f92caf4d986e735b3d00261571dd24a49a07acb0bbdad414b4109aa820e87f389db6d061f3a3d937fa0f18d4a480c12f7785cb3328420cc9ac331cb086261c4098b8f23c9fa03fa03ff3da545281b050100 +0300a9087e22e34e92e81710fca1d4ca8fce0b4c7d3de8e6e86b9c5b86657995e01f721b2a7d018319e20a3f8e247cafcd3eb4580ff780759959e1b0032df17ead140dee9866e55e644228940349bc3ead432f7fb4d3d7c171666caf9f13ccbabc1201000300ac8c16c4682774a0d2f2ca0b42036ba1ddfa3a1441a7b6aa670c81b0ae646b853413645fd39ccbe8787485c0c8b11fb6e9e017eb9128d9e2bf9719ef061c6b4aebd18a8b3f1fdbe9d52761adb110994937c6e6700bcceefb707e088e16cb2c64010000000000000d7d84c58b6ba2606912bfc22babcc4e7cb0fb25dccf855d8f6cd8f5a23ec5b787cd729703008f85871cb8e4b9080187eb161904619f40de79fd3a9d24ca7d91ee6a8156398b189b1f69405bc1160bfc8391ed61c476b0bbb32465d55487f4f2f555f743b14409acef5583289fc689d8b02b5d7ed8fde49a29e7cf588fa24c95a25f85c4ca04010000000000002877c4887936231af6910c4390dad8454d654eeb4fd9b2cd89029c633ccc02b0c15bc12901004944988028c8ac8be372fca445f19679cca20babe6c97ab66c6d631157a35ef89d82099522b3abb1dd1b47b5f6c6054cc499d18b7cd156803e220c3cd47572457533a95af1ba9e4a912d0842d5eede081c7d7207ae8586063261bfc0fff05c440100 +3978253 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000afd1c2a5a496dc7e3a99dba0fb376c2f41f208cef0b4c5b9f0e11358dbaabfa72e2193a08d9682ad79b83d9eb1f7d09f3257cf65c1ac8020eeeddb5f99ccd612afb714fc880bf7ea4978b687a06514225739559f72c34b54df40635caa3da30605000300e6d1f256eb1784bd267b162ac6dd2a39b6ac9c7c77a1abdbb5e706a3421c9aa54d8c0b6f75bb381c021c6ac0cfc367915e8685ba542baec739b87fd01e52fa4f5300fba693e188484c620d5baa1ba61aca27bf8f6fcfa1300b0799fdc644fb730100 +38338560 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00006f9a2ce407e3bf991cc84609f1cb4e58ea1e462976317f2296e2e31ccb3074a634a3f154beb3e8122932cdc0603de2d3240989159bf0a97f343f21fb00271809527164295b56b74db9f4004666300efa93d3bd2a36c0cb34f5fd3efc3a83ce03090500009a3930735f449b478a320b83b24bd6501eb09921bfa5ed72542b9eff0081663def32478bcf21678b3c086a18a1bcbbca0a7396197a96377b22e1eb5220181a597d39b0bb5470066d3f558c27d1ad90246e62efe9e2095278e47050cda7ff735d0100 +44728320 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000046bcc10b27f823f35dd5423ff58017e4105523a99011079f02ec01eebaa44ed38dae0bb6129e2c5f3e515abfaa4555a9e19ffb2a660baaf575a885197e9fc73e61e39e2586889b58c6bcdf6642e7d338296315b2be5bac96d1417795bc86062d01000200fd475549c39f6a7dd9a25b79a1e9f548aad7f59fdc428bf598f7cfc5bbe5b523a807b41b7a98141a802ecb158497e24547f9858adf68e8627b3142e60d0b937d73eb0652af617a0cf35bada170aa7253e581f693b5b06797fee1f4a47a45fc8e0100 +46480329 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +020062d6f21d1b6af924db6f4dce4a1ccaa5a8de16ed03272b2d91405f11ddf842ec39b6bf54d1a94bce719b7441941302557489939d44395aa5113e5beccafdc40bedfa71c6c4deb2663c69a6333a0555027117f8f6c0c222405c12ad6ad16a0201030003000a83e28d975e0e4e09dcedc1677918232d3190f38f75bf43fddea630f023caf9954345682490f45a23a74a37eb4dabb06a99738dc7f8b60df490f58bb9e357014f70b64caa32e9bb9f637cdb323e533b844442f134b20aa45bb0995bde65ae001f08 +51695466 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0300d4f0224c84bde4f18836a127c011c65a89b53288cf73b9941165534cca818d579eabc2c92db0d4a923270df3b6e31e203a9e5f690558aad20bff559a9921560a5547039cfe8d7826401595c42f40088589f764c950df11818a795d910ae4eb1a01000300cabf631b73bd6389f55f29e69913f9b6d05056c4a4e0f031403812f1194de88d7a1532afd95afd3d6a3fd1ee4eeeb3c4037170eebd13df9364d64990d2f66238e5c160737b688794aeb0360ad6659aea8f5b899f6d101f4c222d9159fc432a100200 +42598400 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02008dc89b580eafe8771f2ea25b9696868eff6b9ed977d49871e99e06c642a7edee23c3f6e5193064d417293b8a376db61da1946a9a92fc485b644f36b48b808a48c8693ea1935cd7f5a1503c8524a61f1da2424ce46a1846bfdb3520d31934eb1c0201030087be7e85de4efd409610f57f369f61d266d608f72a14f6d874c6376d0a92ebc27009809bc367d235e3f3ce4ff54fafc36a9a7b42f896c5153ec5cd5ad08b420180ce6b323bb5529aee1d7925896de99664f11b6336181698dbd7cb7106e2c5010500 +62252688 +0 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +00009c2fc4f14787d457975518c99edbe03f0fbf825c6960428fb0bc437ebd33d3c5f4b587c6dff921ec21c2b9ff91ee3d41bef059e5a7192cd60c9ddd43286ec926b15932e5547efb8d20a66ddffafd0a59679f468dba5cf155fafdd0b4cda54d440100 +0100826350ce52ac83cdec79fdcb314c729a33d25e099d41b1f64f91a9aa348bb170c7b13443e68b9cacf90fa6c78836ca7eb5a66031caed8b1f5d833bb395acc14e991e0fe405cf4401e419dcf07512bc49a8685a3ee49abeffbcf144c78903664a01000200e0206d98389e2adc18bc648344c1a695b30e39fd18af4786f84637d35d0d506e7dca16301089d33b09eca42db84ca3475894bb50a8c420cd8368d0a8506c3f35db603d350cef7fa81541bd4d4b214b60fc5e433e13b9f08d78e4f7eb049779620100000000000023cc10c9a7518e13dfff1205af22218a196cc6d45d5cda20e1629148556c244e720e25cd01001c52c3b108964a86f24892ee1b556fe1ba3363708e52b063b85f22c14324cb3c0d3e3e84f02974a52ec22da367b1a1c55be5c2bfa871de166ff21b1658d5b7065f6bbb5c2744b80b2ee32e568a185b0ee88d4199e5c855a850c60d27eb512f13020000000000006b649484c9cc288d1fc16ba34c558c269b29bbb0ec5095217e13b853e0baea8614c0af8f0300c0cbb79dcecd71d0c0ed4dcdf3af2e7b41fd9dde48a8297d6baa4fb9fbbd3283196b2cb945160701f77f09325067fae99889edca253a7926638792a20f104e4eb136373832c461dcde07f7d83d8206c29eca3ad9bff04221fc78ee5cb91c70020100 +10557222 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02008e5056620a1f7a28222d24d1282643f0134e7b3d2e9d63b50fc718e53cfa7ee25a5afbf8fb6179cef38362aeed79d0ccc521f5bd0ee6b91a05b1332e51e06b6a61376a71e620456e073bd65f8edb4562f3d497051106713873903f5876f3be02010001002c70f6154d2683e15e9b1148194cb2a2f2d87564a5e5d5c332cadcbb94be5f3ddf24544dc549f17b23adb0772af55e2d85f523bd578afb7bb985c8b7c9034700a760a36dc04b309799cd66c0fa559fbd51b6c77e61e47f5d9ef0f538525610030400 +50458582 +0 +18d26c14e2924725ac4e11c7913217c558c3b4a034a3a97838cd835b9793480d +1024 +02001e71bbc55cd6f524faa07bcc3770fee35ff1834cb1817194ba8b2e1a2fea376547a7fb8259f5909b4b6c85657c3561c26d1e70ce610fd0d4f5758dce2d639d37f710e17e0c0619caa9de861e71c0651123d3a295b2db62539030e2c082bad9170100 +02007a92aa8adbfc3145ec8e676015b4c7fbeebe8684370440a7cc87f5df91b407bcc0331265e34ad6de8cc3f4a80fa4dc043e02404c02fff510328be5c5c9233735839d4290cb01876dd8149ebceb671fc8df6b1084e00a5a0918a740afb3e5542c01000200f72585c08a1346c3ad74fa2fe0df96a91d91472e47edc02262b69a3b7feb4fecd0333130734b702a00ba23e712009832d320a7b125065494722c6447572a7a56f46530ea2e9b20619af949340989359208bfdc3cd05793eca05ddbc45ffea98f010000000000006522a0fb3bceb74c96806f3448e8dc7d269beb9736e75f9a9cc1ebb7c1627b7835a0c3870100d129279f52f22b74793f2098d0616205ae73a1e76ad5a1a748885a446b9ff5523c3fcbe72c1c9ee0bdb24c14634c897be434825928b9aa6f053b4720ba93105945373c8dec42937d4b04ee1b8bfd77141478c2bb78fbdc03966aa09e18026a4c010000000000012f67e0cad5e1a15da2ba6f9a7e9f28eda304b3b5fe4585ae4656bf86fab1199092d598d700003497e9a9adc6c98ee62992f0ee025e644a8fca11e50162339badc2f4752873019fb4f3b5b3ff3e57459f4f26cad1c71863e4d69187442ba1b56cbb27d3abaa26f548d4041094f116f6eb567ee8d3e492b7922bb6179faf63376db7941b010c040100 +28817748 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0100826350ce52ac83cdec79fdcb314c729a33d25e099d41b1f64f91a9aa348bb170c7b13443e68b9cacf90fa6c78836ca7eb5a66031caed8b1f5d833bb395acc14e991e0fe405cf4401e419dcf07512bc49a8685a3ee49abeffbcf144c78903664a0100 +0000464205daced667d03a7263a78f65f47950a75b7186b8f1b461e7756962cf0c161540fc28030add3e127f3f587a5ad07fb0b0231d756b11d0beddcd80af949f028541fca7f1005935b1c927d443670cf6d20703b0cd64e0c6fcc268cf462f5a1301000000cc57438dc07534658e96e811b1524ba7ccdcf2f4982df58e57f5612fada74db57b6133c729404e0f11b7b5aa15cb6f37b21829c5f72a6aa06e24b78184587d555fa00835ee05eb19db652b4901d94146cc12de37c118731bff1e4959f9f0985d010000000000001367f8dfa65e0b18c61dd9e2b3b3a76928e115eddf4e3da8c0bbfc7c22771fc6bd0fbe6d0200f5b26cf774f406b3954c2bd0e4a86d3b9f5b136a394f0a2ed92b76b4614ab7283eef937749f6f5d2e966abea25058d9464b3ca05f785c22895dc46300ab0303515df159199b7d96efaf6c4ac345cfd898bb4842be1f4c90b4db9377c451c9119020100000000003a37e8e5b81d33e74ba262ce51ebc7bec8fc10268e25be9c47ca50207524f57ed1531d1b0300f06dd85a1ee579b6ce9e1d2470bfdaefb79786ab1b0a208e7397c495f37512abeb46ea3e7aa8ce8857a9c99c384715f98b2056f2b8f86c02cc7a267e08346250bf415e3f7d2c3d8c508d449b6f1c00739a39d7475162388bc29517e783470e660100 +5723152 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0300a9087e22e34e92e81710fca1d4ca8fce0b4c7d3de8e6e86b9c5b86657995e01f721b2a7d018319e20a3f8e247cafcd3eb4580ff780759959e1b0032df17ead140dee9866e55e644228940349bc3ead432f7fb4d3d7c171666caf9f13ccbabc120100 +00009c2fc4f14787d457975518c99edbe03f0fbf825c6960428fb0bc437ebd33d3c5f4b587c6dff921ec21c2b9ff91ee3d41bef059e5a7192cd60c9ddd43286ec926b15932e5547efb8d20a66ddffafd0a59679f468dba5cf155fafdd0b4cda54d44010001003f3f998d3a920fcd690c076560f5ba0e98e21c738bf0400a41cb8f232acd78d25197a8bd18e25e6485efb2894547a6a77fa071aecd999839bf8d149180744101c6e17dea4eb97a01c22f8892c5d44cd1108b74107a08e3298f18e8c010f91b00322200000000000431d49ff5c2546f07e2651ea0229d5b55cae3f3b9cfb7de79e4f1808555f88bfd0548370200bcdafe2e7f42bba7006b116cc15d71f4135317dcfb90aaf232750f6bc536e781da79f690ddef216b4fb7c7a67b3515fb8d8e3a5265c0cc595bd4d71c737bcc038ddf21d6cbbcf2e068d5da00872e947ff6f81a31c7b9d0f3d474abb7d6c6dd05110200000000000c94b4820395a38dd4b7b264ac336db0e7e96429cb2eee03bceed36ef67a5f5a5a160c5f000021e1d4135ec5f959f813187cf79177deb2ac6c26d111f745fbbdca06f6c6d5f24472f62ced515dceda5220f3dea4889ec5bba0b529f4eb96322f33f56f70123930cd8592f408b4f653dd1446a777caf8ac7f715a4aeb2b330774744d9e11f1380100 +1236884 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000d434a63505490261760cd02b0d705ee2442a680d1b7320dac68e61834afda91d68c7c94672e3cc33fbacbdcc980a140ce61c303a96113679a45cb83e6c5bbd32d510bdb27781ae12876902d85a321f9d72f157b47ab5d230978692b1a3b93d5f010003002124828d3a0042092464363d40a48000a33f5840f9d8fd6cab02ecc88adf83fa245515a1cd4e844adf0f41002e1d1fcfc80c62f3a99372da93dcfaefdc294e048faf8ca510e70b33c4bb8e8bf3ba33fc8860594e4142ce94d33fe42a1fef020b0201 +55377920 +0 +26ed7314b55979dc15087a1b70711e4ecda38c4eaba6f3ab75f1a0d42f27c74d +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200a0f839b39f425c784f80c0be02a8ad7438062539fcc179a5a854e460275a48997015a45524cd34f68a3ed5a36139caff793bdedaf2eb8e9589e0f457813d6356b1c82d86734a56c22c9be14a03597493cd452f988afe4b83ce6f817c3642cd0601000200831a7c06818949e232227ff9c10535aa6dcb5101460f61ae7c66903f46cca539abb4b22c7fbc57716724a00ecd8b479fa38d7153602a59a9572c5ff67ed3ca01099eb8e9ef25a29056f90bcbc1d43f7c3a061d39bafe5c5c339ea31a31dc610901000000000000150ea0d6d9f8303e0822a938cbd3e212680d28681951ea0e11d597bb14b4b89b07440b3101003c25230d49cb0d1d0f31d836285dcb11291ea471a976e557bb680aa9cc956f8cf9b195d55fb080dbfc553c4d74172ea0984aa83fe457804e6297c95ced1e0752fb9902da0774c0abee03a6eb61831b53772b01dfa2b419ef575e9c212bd1ff65010000000000003efd00cfe074f29923a10d2348dfd133595e3ce73852b1079502eb632a1f56dcce05e55501007972182356d390c2c5d8adb172c694f0474fabe3810aab4003983baabbe6fabeac31afb9f1d163fb33f72a5105d2473d2a7f1d2d0aab1816ab48010ec775ba053aafd7515c98a321938e33e4d8d6022207e9d1462df8217fec326a7a177d7a0b0200 +5986998 +2 +e7b3e4b056f92e04ddf2b9933acd48ceb06a92a2a4f136857bff47c5f2de3b79 +1024 +0000464205daced667d03a7263a78f65f47950a75b7186b8f1b461e7756962cf0c161540fc28030add3e127f3f587a5ad07fb0b0231d756b11d0beddcd80af949f028541fca7f1005935b1c927d443670cf6d20703b0cd64e0c6fcc268cf462f5a130100 +000028f338f76f84ab9913a147e3440b38312179235dcdbddccd2051031f0cda85b5d7ec2b4bfb462230d8d0306fa8c99473191a9dafd575792533b3d7aae7e66d1893a4b1e03c5f73b88a0de84748792fba73c2e2e6dcff1bbeba008bb9594ce62001000300e5fc3eff8f4c50fb4605bfc618a12f86fc68ec07e0270555080edbdc811457f65571dfcd35f7f6007932ccaadf0f74dbfc9860f41fe572784f76f0c1d8e1cd0234e4df4a154c877c5da122ab1fa74db63044a8b9f44234b6d77fb76f910656000100000000000002628ced2a1addd7cf994417aea5a3c2f16a5d7f8a3666e5eeba4cb5516eb5b923bcd21102004236b83a9331715b94d18682bf02405a484eb59095873b40c918d6600885985cde64b2efcf8a86999edc0f2b049de259452a065cef553829b07932d7eede7b6581d7739de11915e4d6f24edb8323ac15869b4d0134dce0970f4b3720618b3768010000000000000727a4cf80c4a27d58692047dd1d337627c1dd0c41fece84d85c5713d2b352937c6b1acb00001e6e1c4a07fac293835408c6d2e6ae1394450daf2060b3aecf4a0454e5b32b362047469704feecfebb14cd054cf18b601c130f5875f661152bd39c745ac96e063773e37679cbae670b0fa73b27fb210d75631269883ed0120a2c7ba188e05b030100 +703470 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000e8cb95c4065b28fd4d839a0b26e24466c52009389e3ff266efc09355e38185f93592ac9407d9c16c4261f98c2f01d4137807786959c29aaeed8c28f9e94c3d2ac108ebbd8aa1eb7213791c7dc141d1d9bb9ccd36efc0c9d571b9a47fbf96094a01000000a4b0c9ffd3d4818d9b461565276d0e2ea3c0232ee45d94d435e5687ff4b733029cf0a245f8134e09ef112b6e79fbf6b5181e40ca12d8464836eac850946beb648bbc34ba0d9e414e6717b2266bb4ba4dd7449d51c79919a256a864c940fc034c0100 +59637760 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +030008a96564cb454aaf2a3714bd002bacfd34808f305f90af875e67a9da88328d574bf2630f5e4752e06946b9922c61b04e784c1bb2897696c94b58c42047fddf20a31eba11c3ae1cf70e5cc6c3b7ec881fa0f0ce106f8b4aa7c049c3b82b7e653e01000200e847068d139fd60ddae381a52677c3062a09f7da6c1f98e72983997d0edb0283d013b8c0bb7ba596e726feccd6b3dfaa3776b9066da39d41aa14934f7b96b8152b4660cccf1971bc1408ea2219ff3b05eb75f6f5603059b3e2388d1730d4af130300 +63897600 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02006e4ce82a9f0c3901edb58d812ca2d1b8e92a4d324bb85cd5fbdfcf42f7db92a2aeb5bb48ba4e0f174237dcb946818f11e655ec5ecb0b5ecb4898fb7cffdb8a81835cb55fd6cf3022cb298738e6117bc0970a41d45edc6767c566b28116b17630010003004ccc6fadd5fa2af1d662b681e5295524a50fae16a4aebc260b2f80e440cd894130f229047f7c0a88d828f35b7017ac68f0eb399c43c04b964297de376847cb02ed3767c925bd03e09e71f0922aa0d7fffa4b2031ef46d36f417f471bc4466d022505 +67975840 +0 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0200be72297217461354a67792bede4fedb905009e5b18965baf9b4e740a2aa21dae7f1f3ac4501080b7bd2c40d629c2f5f38ccf1b331631ba6fcb39b6b67a917b2f0506488efd627f120a2b6f3cfc49ae57131b5563b00b40ab8b618db306b4cc5201000100c0e1b99f5721b83a2e84ec112163f68554db89febcf82d0b38dbb9812465b17df07997c34c28fe32b2adb9cadd972587c251bf6ea418ba5081e096b367b7ae7aa93c53961652057105fb3eb69001d051a10cdf58ca6dc43b37806f61a75331660100 +70601505 +0 +f2b06664b940c99631c799e30d8fdedc6155e0f7bed8687f1d635d67a091ade2 +1024 +000057b8a32f3a6b8386f995a2150ca3d825ae80de7418bc2f6a6244d0d03203f5fcf69a7ab190534b3d48902d398505d9102d2687171125a15711fc167b2349a004c4e765c963020f0a3ef7c86abf2dd75a78efe671010eddda726ed92ba83037140100 +01004a8999d30d601a098a1a4e2b8d5bf22b054d57d42ca4e1da5c155722ff06efb0cfe98fd24113159346a4e3e3f24ceaecb213e0b916d85f709b207018ab82fd732b01f78fa8089d790ed3698cbb5c6b9c33e27f2f8708bf3bf9f04ec0b123881e0100020005b6ca3a11d4bac7d457f4028ffea9d92610c5b75eee741b50d8c4ab50999af6113dff897a4a09a322e5780dd4acf88d3c45d6ede26db5f69cc3da099964bd0044e102797f3cd0cb5161bfd82c0e1e4a42634b0b252e1140e337de2297234f0602010000000000150ea0ede1de9d3e1ceb43c1935a4320bc4654a1fda667a2621be1d8602867db0b7eeafd030080323d77cb3ea83242ae587228533f6b9235a08233f3e3cfc70061deca51936463bb0da89c050b80f4d05f5f6dbd473dbe2651045f95965f6b42c3647e9e3c2ab3712746e3bd11a4f125de0a8bad53ae4da7b6550099cfc3e25692bdb7364534010000000000003efd00fefa515653dff245575b21cec74b60840d7a67844a64439f8b8e9e29442128b0910000816d70b759686797845f692ddc3d20970f7156acc644edd53b06e6a55a36da823ebdfe394122cb6b8ecd5d9505293849501807b97910b136b32c408b5ea4a90e0c08aa2b39103aeee3b81bf39420e6610a28351557750c426a71249742ea081d0401 +5986998 +2 +76fdd5ab94a59aa65863bb1d0103fee9e266a4e88db9b5d14c9b06424e628304 +1024 +08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +030066763f27ed2b02cb5cb2a9bb31e8e4878031eeef5294ee9bcfb1d51bed0a8fe7483dafce2c3b70d45ae7aa27afb937ad512e72d32b5b22fe0f791955b509e41e95e4d16b7597fd451b602a79f2224cf48e25d870f294f5030a93948bc179561401000100b7d15ab1a894e8e0f10107b3b3a97ddea6f3632a9bd62219f3938971bb950c6b73b33a283e83bde9564d000fba3e03294f74badca9c3a352c3dd6427eb9a203821fd8ffd5411564584f44069148b433ca1a790cae413b25248a0ca09256ce8240100 +77875267 +0 diff --git a/crates/chia-vdf-verify/tests/integration_tests.rs b/crates/chia-vdf-verify/tests/integration_tests.rs new file mode 100644 index 000000000..120e626fc --- /dev/null +++ b/crates/chia-vdf-verify/tests/integration_tests.rs @@ -0,0 +1,435 @@ +use chia_vdf_verify::bqfc::BQFC_FORM_SIZE; +use chia_vdf_verify::discriminant::create_discriminant; +use chia_vdf_verify::form::Form; +use chia_vdf_verify::integer::{fdiv_r, from_bytes_be, num_bits}; +use chia_vdf_verify::nucomp::{nucomp, nudupl}; +use chia_vdf_verify::primetest::is_prime_bpsw; +use chia_vdf_verify::proof_common::{ + deserialize_form, fast_pow, fast_pow_form_nucomp, get_b, serialize_form, +}; +use chia_vdf_verify::reducer::reduce; +/// Integration tests for chia-vdf-verify using real test vectors from chiavdf. +/// +/// Test vectors generated by building chiavdf (C++/GMP) and running: +/// seed = "test_seed_chia" (14 bytes) +/// disc_bits = 512 +/// x = generator form (a=2, b=1) = the 0x08 BQFC encoding +/// iters = 100 +/// y = x^(2^100) with discriminant D +use chia_vdf_verify::verifier::check_proof_of_time_n_wesolowski; +use malachite_base::num::basic::traits::{One, Zero}; +use malachite_nz::integer::Integer; + +fn hex_decode(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() +} + +/// Test vector generated by chiavdf C++ implementation. +/// seed = b"test_seed_chia", disc_bits=512, iters=100 +/// x = generator form (BQFC encoding 0x08...) +const ITERS: u64 = 100; +const D_BYTES_HEX: &str = "d0cb181074454b32a0e0fc5e65a1d7625ea43756eaa8de13a9c750c79f7aa60151f065cd5775516159c28713c1e74ced6520f8f5c55129f32f865b28cf7fe8e7"; +const X_S_HEX: &str = "08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; +const PROOF_BLOB_HEX: &str = "020020417eb39c4e14954a817af644fc13d086c26dddab8afea12415b5e685f7883f5740ba01cb75220081c8aba7854cbd52010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + +fn get_discriminant() -> Integer { + let d_bytes = hex_decode(D_BYTES_HEX); + -from_bytes_be(&d_bytes) +} + +/// Full integration test: verify a real Wesolowski proof from chiavdf. +#[test] +fn test_verify_real_proof_depth0() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + + let result = check_proof_of_time_n_wesolowski(&d, &x_s, &proof_blob, ITERS, 0); + assert!( + result, + "check_proof_of_time_n_wesolowski failed on known-good proof" + ); +} + +/// Verify that the discriminant we generate matches the test vector. +#[test] +fn test_discriminant_matches_testvector() { + let seed = b"test_seed_chia"; + let d_rust = create_discriminant(seed, 512); + let d_expected = get_discriminant(); + assert_eq!( + d_rust, d_expected, + "Rust discriminant doesn't match C++ discriminant" + ); +} + +/// Verify the discriminant is negative and ≡ 1 (mod 8). +#[test] +fn test_discriminant_properties() { + let seed = b"test_seed_chia"; + let d = create_discriminant(seed, 512); + assert!(d < 0i32, "D must be negative"); + + let r = fdiv_r(&d, &Integer::from(8i32)); + assert_eq!(r, Integer::from(1i32), "D must be ≡ 1 (mod 8)"); + + assert_eq!(num_bits(&d), 512, "D must have exactly 512 bits"); + + let p = -d; + assert!(is_prime_bpsw(&p), "magnitude of D must be prime"); +} + +/// Verify x (generator form) can be deserialized. +#[test] +fn test_deserialize_generator_form() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let x = deserialize_form(&d, &x_s).expect("failed to deserialize x"); + assert_eq!(x.a, 2i32, "generator form: a should be 2"); + assert_eq!(x.b, 1i32, "generator form: b should be 1"); +} + +/// Verify y can be deserialized and has correct discriminant. +#[test] +fn test_deserialize_y_form() { + let d = get_discriminant(); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + let y = deserialize_form(&d, &proof_blob[..BQFC_FORM_SIZE]).expect("failed to deserialize y"); + + let disc = &y.b * &y.b - Integer::from(4i32) * &y.a * &y.c; + assert_eq!(disc, d, "y form has wrong discriminant"); + assert!(y.is_reduced(), "y must be reduced"); +} + +/// Verify form exponentiation preserves discriminant. +#[test] +fn test_form_exponentiation_preserves_discriminant() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let x = deserialize_form(&d, &x_s).unwrap(); + let l = Form::compute_l(&d); + + for &exp in &[0u64, 1, 2, 10, 50] { + let result = fast_pow_form_nucomp(&x, &d, &Integer::from(exp), &l); + let disc = &result.b * &result.b - Integer::from(4i32) * &result.a * &result.c; + assert_eq!( + disc, d, + "discriminant changed after exponentiation by {}", + exp + ); + assert!( + result.is_reduced(), + "form not reduced after exponentiation by {}", + exp + ); + } +} + +/// Property: x^0 = identity. +#[test] +fn test_zero_exponentiation_is_identity() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let x = deserialize_form(&d, &x_s).unwrap(); + let l = Form::compute_l(&d); + + let result = fast_pow_form_nucomp(&x, &d, &Integer::ZERO, &l); + assert_eq!(result.a, 1i32, "x^0 should be identity (a=1)"); + assert_eq!(result.b, 1i32, "x^0 should be identity (b=1)"); +} + +/// Property: x^1 = x. +#[test] +fn test_one_exponentiation_is_self() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let x = deserialize_form(&d, &x_s).unwrap(); + let l = Form::compute_l(&d); + + let result = fast_pow_form_nucomp(&x, &d, &Integer::ONE, &l); + let mut x_reduced = x.clone(); + reduce(&mut x_reduced); + assert_eq!(result, x_reduced, "x^1 should equal x"); +} + +/// Property: nudupl preserves discriminant. +#[test] +fn test_nudupl_discriminant() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let x = deserialize_form(&d, &x_s).unwrap(); + let l = Form::compute_l(&d); + + let mut f = x; + for _ in 0..10 { + f = nudupl(&f, &d, &l); + reduce(&mut f); + let disc = &f.b * &f.b - Integer::from(4i32) * &f.a * &f.c; + assert_eq!(disc, d, "nudupl changed discriminant"); + } +} + +/// Property: nucomp is commutative (f*g = g*f after reduction). +#[test] +fn test_nucomp_commutativity() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let x = deserialize_form(&d, &x_s).unwrap(); + let l = Form::compute_l(&d); + + let y = nudupl(&x, &d, &l); + let mut y = y; + reduce(&mut y); + + let mut fg = nucomp(&x, &y, &d, &l); + reduce(&mut fg); + + let mut gf = nucomp(&y, &x, &d, &l); + reduce(&mut gf); + + assert_eq!(fg, gf, "nucomp must be commutative"); +} + +/// Property: reduction is idempotent. +#[test] +fn test_reduction_idempotent() { + let d = get_discriminant(); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + let y = deserialize_form(&d, &proof_blob[..BQFC_FORM_SIZE]).unwrap(); + + let mut y2 = y.clone(); + reduce(&mut y2); + assert_eq!( + y, y2, + "reduction should be idempotent on already-reduced form" + ); +} + +/// Property: BQFC round-trip for generator and identity forms. +#[test] +fn test_bqfc_roundtrip_special_forms() { + let d = get_discriminant(); + let d_bits = num_bits(&d); + + let mut identity = Form::identity(&d); + let bytes = serialize_form(&mut identity, d_bits); + let f2 = deserialize_form(&d, &bytes).unwrap(); + assert_eq!(identity.a, f2.a); + assert_eq!(identity.b, f2.b); + + let mut generator = Form::generator(&d); + let bytes = serialize_form(&mut generator, d_bits); + let f2 = deserialize_form(&d, &bytes).unwrap(); + assert_eq!(generator.a, f2.a); + assert_eq!(generator.b, f2.b); +} + +/// Property: BQFC round-trip for the y form from the test vector. +#[test] +fn test_bqfc_roundtrip_y_form() { + let d = get_discriminant(); + let d_bits = num_bits(&d); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + + let original_bytes = &proof_blob[..BQFC_FORM_SIZE]; + let y = deserialize_form(&d, original_bytes).unwrap(); + + let mut y_for_ser = y.clone(); + let re_serialized = serialize_form(&mut y_for_ser, d_bits); + assert_eq!( + re_serialized, original_bytes, + "BQFC serialization of y should match original bytes" + ); +} + +/// Verify that GetB matches: B computed from (x, y) matches what's embedded +/// in proof verification (the proof is only valid with the correct B). +#[test] +fn test_get_b_deterministic() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + + let mut x = deserialize_form(&d, &x_s).unwrap(); + let mut y = deserialize_form(&d, &proof_blob[..BQFC_FORM_SIZE]).unwrap(); + + let b1 = get_b(&d, &mut x, &mut y); + let b2 = get_b(&d, &mut x, &mut y); + assert_eq!(b1, b2, "GetB must be deterministic"); + assert!(is_prime_bpsw(&b1), "B must be prime"); + assert_eq!(num_bits(&b1), 264, "B must be 264 bits"); +} + +/// FastPow correctness: 2^100 mod B. +#[test] +fn test_fast_pow_correctness() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + + let mut x = deserialize_form(&d, &x_s).unwrap(); + let mut y = deserialize_form(&d, &proof_blob[..BQFC_FORM_SIZE]).unwrap(); + let b = get_b(&d, &mut x, &mut y); + + let r = fast_pow(ITERS, &b); + assert!(r < b, "r must be < B"); + assert!( + r != Integer::ZERO || ITERS == 0, + "r should not be 0 for typical cases" + ); +} + +// Second test vector: seed = "chia-vdf-rust", iters = 200 +const ITERS2: u64 = 200; +const D_BYTES2_HEX: &str = "c3ef34d02017540ef26d88057bbfc778da12ed572b99f8707834ed344577c210b1f9287f54a536913177bf5880a4a51b6bfa42445f3fbcd082b695e38c2066d7"; +const PROOF_BLOB2_HEX: &str = "030033205ea6d1ab367757073029f1462eb2fcc79749871d0b576f7a392adac84f56f46100e477d59353376f82a3eb56720d010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + +/// Second test vector: different seed, 200 iterations. +#[test] +fn test_verify_real_proof_second_vector() { + let d = { + let d_bytes = hex_decode(D_BYTES2_HEX); + -from_bytes_be(&d_bytes) + }; + let x_s = hex_decode(X_S_HEX); + let proof_blob = hex_decode(PROOF_BLOB2_HEX); + + let result = check_proof_of_time_n_wesolowski(&d, &x_s, &proof_blob, ITERS2, 0); + assert!(result, "second test vector verification failed"); +} + +/// Wrong iters should fail verification. +#[test] +fn test_wrong_iters_fails() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + + let result = check_proof_of_time_n_wesolowski(&d, &x_s, &proof_blob, ITERS + 1, 0); + assert!(!result, "wrong iters should fail verification"); +} + +/// Wrong proof blob size should fail. +#[test] +fn test_wrong_blob_size_fails() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let proof_blob = hex_decode(PROOF_BLOB_HEX); + + let result = check_proof_of_time_n_wesolowski(&d, &x_s, &proof_blob[..100], ITERS, 0); + assert!(!result, "truncated blob should fail"); + + let mut bad_blob = proof_blob.clone(); + bad_blob.push(0); + let result = check_proof_of_time_n_wesolowski(&d, &x_s, &bad_blob, ITERS, 0); + assert!(!result, "extended blob should fail"); +} + +/// Corrupted y bytes should fail verification. +#[test] +fn test_corrupted_y_fails() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let mut proof_blob = hex_decode(PROOF_BLOB_HEX); + + proof_blob[5] ^= 0x01; + let result = check_proof_of_time_n_wesolowski(&d, &x_s, &proof_blob, ITERS, 0); + assert!(!result, "corrupted y should fail verification"); +} + +/// Corrupted proof bytes should fail verification. +#[test] +fn test_corrupted_proof_fails() { + let d = get_discriminant(); + let x_s = hex_decode(X_S_HEX); + let mut proof_blob = hex_decode(PROOF_BLOB_HEX); + + proof_blob[BQFC_FORM_SIZE] = 0x00; + let result = check_proof_of_time_n_wesolowski(&d, &x_s, &proof_blob, ITERS, 0); + assert!(!result, "corrupted proof should fail verification"); +} + +// --------------------------------------------------------------------------- +// vdf.txt stress-test vectors from chiavdf (1024-bit discriminant, depth 0..7) +// --------------------------------------------------------------------------- + +const VDF_TXT: &str = include_str!("fixtures/vdf.txt"); + +struct VdfVec<'a> { + challenge: &'a str, + disc_bits: usize, + input: &'a str, + proof: &'a str, + iters: u64, + depth: u64, +} + +fn parse_vdf_txt(content: &str) -> Vec> { + let lines: Vec<&str> = content.lines().filter(|l| !l.is_empty()).collect(); + let mut out = Vec::new(); + for chunk in lines.chunks(6) { + if chunk.len() < 6 { + continue; + } + out.push(VdfVec { + challenge: chunk[0], + disc_bits: chunk[1].parse().unwrap(), + input: chunk[2], + proof: chunk[3], + iters: chunk[4].parse().unwrap(), + depth: chunk[5].parse().unwrap(), + }); + } + out +} + +fn verify_vdf_vec(v: &VdfVec<'_>) -> bool { + let challenge = hex_decode(v.challenge); + let d = create_discriminant(&challenge, v.disc_bits); + let input = hex_decode(v.input); + let proof = hex_decode(v.proof); + check_proof_of_time_n_wesolowski(&d, &input, &proof, v.iters, v.depth) +} + +/// Fast smoke test: one depth=0 and one depth=2 vector from vdf.txt. +/// Exercises the 1024-bit discriminant path in ~35 ms (release) per vector. +#[test] +fn test_vdf_txt_sample() { + let vecs = parse_vdf_txt(VDF_TXT); + + let d0 = vecs.iter().find(|v| v.depth == 0).expect("depth=0 vector"); + assert!(verify_vdf_vec(d0), "depth=0 vector failed"); + + let d2 = vecs.iter().find(|v| v.depth == 2).expect("depth=2 vector"); + assert!(verify_vdf_vec(d2), "depth=2 vector failed"); +} + +/// Full vdf.txt test suite — all 110 vectors (depth 0..7, 1024-bit discriminant). +/// Skipped by default due to runtime (~15 s release / ~70 s debug). +/// Run with: cargo test --release -- --ignored test_vdf_txt_all +#[test] +#[ignore] +fn test_vdf_txt_all() { + let vecs = parse_vdf_txt(VDF_TXT); + let mut passed = 0; + let mut failed = 0; + + for v in &vecs { + if verify_vdf_vec(v) { + passed += 1; + } else { + failed += 1; + eprintln!( + "FAIL depth={} iters={} challenge={}...", + v.depth, + v.iters, + &v.challenge[..12] + ); + } + } + + assert_eq!(failed, 0, "{}/{} vectors failed", failed, passed + failed); +}