use core::fmt;
use hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
use num_bigint::BigUint;
use secp256k1::constants::{CURVE_ORDER, FIELD_SIZE, GENERATOR_X, GENERATOR_Y};
use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing, Verification};
use crate::prelude::*;
use crate::sighash::EcdsaSighashType;
pub const SCHNORR_SIGNATURE_SIZE: usize = 64;
pub fn verify<C: Verification>(
secp: &Secp256k1<C>,
msg: &[u8; 32],
sig: &[u8; SCHNORR_SIGNATURE_SIZE],
pubkey: &PublicKey,
) -> bool {
verify_inner(secp, msg, sig, pubkey).is_some()
}
pub fn sign<C: Signing>(
secp: &Secp256k1<C>,
msg: &[u8; 32],
secret_key: &SecretKey,
) -> [u8; SCHNORR_SIGNATURE_SIZE] {
sign_inner(secp, msg, secret_key).expect("BCH Schnorr s-value is zero (probability ~2^-256)")
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Signature {
pub signature: [u8; SCHNORR_SIGNATURE_SIZE],
pub sighash_type: u8,
}
impl Signature {
pub fn from_slice(sl: &[u8]) -> Result<Self, Error> {
if sl.len() != SCHNORR_SIGNATURE_SIZE + 1 {
return Err(Error::InvalidSignatureSize(sl.len()));
}
let mut signature = [0u8; SCHNORR_SIGNATURE_SIZE];
signature.copy_from_slice(&sl[..SCHNORR_SIGNATURE_SIZE]);
Ok(Signature { signature, sighash_type: sl[SCHNORR_SIGNATURE_SIZE] })
}
pub fn serialize(&self) -> [u8; SCHNORR_SIGNATURE_SIZE + 1] {
let mut out = [0u8; SCHNORR_SIGNATURE_SIZE + 1];
out[..SCHNORR_SIGNATURE_SIZE].copy_from_slice(&self.signature);
out[SCHNORR_SIGNATURE_SIZE] = self.sighash_type;
out
}
pub fn to_vec(self) -> Vec<u8> { self.serialize().to_vec() }
pub fn sighash_ecdsa_type(&self) -> EcdsaSighashType {
EcdsaSighashType::from_consensus(self.sighash_type as u32)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
InvalidSignatureSize(usize),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::InvalidSignatureSize(n) => {
write!(f, "invalid BCH Schnorr signature size: {} (expected 65)", n)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
fn generator() -> PublicKey {
let mut buf = [0u8; secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE];
buf[0] = 0x04;
buf[1..33].copy_from_slice(&GENERATOR_X);
buf[33..65].copy_from_slice(&GENERATOR_Y);
PublicKey::from_slice(&buf).expect("secp256k1 generator is a valid point")
}
fn to_be_32(n: &BigUint) -> [u8; 32] {
let bytes = n.to_bytes_be();
debug_assert!(bytes.len() <= 32);
let mut out = [0u8; 32];
out[32 - bytes.len()..].copy_from_slice(&bytes);
out
}
fn challenge(r: &[u8; 32], pubkey: &PublicKey, msg: &[u8; 32], order: &BigUint) -> [u8; 32] {
let mut preimage = [0u8; 32 + secp256k1::constants::PUBLIC_KEY_SIZE + 32];
preimage[..32].copy_from_slice(r);
preimage[32..32 + secp256k1::constants::PUBLIC_KEY_SIZE].copy_from_slice(&pubkey.serialize());
preimage[32 + secp256k1::constants::PUBLIC_KEY_SIZE..].copy_from_slice(msg);
let hash = sha256::Hash::hash(&preimage).to_byte_array();
to_be_32(&(BigUint::from_bytes_be(&hash) % order))
}
fn is_quadratic_residue(y: &[u8; 32], field: &BigUint) -> bool {
let exponent = (field - BigUint::from(1u32)) / BigUint::from(2u32);
BigUint::from_bytes_be(y).modpow(&exponent, field) == BigUint::from(1u32)
}
fn rfc6979_nonce(secret_key: &[u8; 32], msg: &[u8; 32]) -> [u8; 32] {
const ALGO16: &[u8; 16] = b"Schnorr+SHA256\x20\x20";
fn hmac(key: &[u8; 32], parts: &[&[u8]]) -> [u8; 32] {
let mut engine = HmacEngine::<sha256::Hash>::new(key);
for part in parts {
engine.input(part);
}
Hmac::<sha256::Hash>::from_engine(engine).to_byte_array()
}
let mut v = [0x01u8; 32];
let mut k = [0x00u8; 32];
k = hmac(&k, &[&v[..], &[0x00u8], &secret_key[..], &msg[..], &ALGO16[..]]);
v = hmac(&k, &[&v[..]]);
k = hmac(&k, &[&v[..], &[0x01u8], &secret_key[..], &msg[..], &ALGO16[..]]);
v = hmac(&k, &[&v[..]]);
loop {
v = hmac(&k, &[&v[..]]);
if v != [0u8; 32] && Scalar::from_be_bytes(v).is_ok() {
return v;
}
k = hmac(&k, &[&v[..], &[0x00u8]]);
v = hmac(&k, &[&v[..]]);
}
}
fn sign_inner<C: Signing>(
secp: &Secp256k1<C>,
msg: &[u8; 32],
secret_key: &SecretKey,
) -> Option<[u8; SCHNORR_SIGNATURE_SIZE]> {
let order = BigUint::from_bytes_be(&CURVE_ORDER);
let field = BigUint::from_bytes_be(&FIELD_SIZE);
let nonce = rfc6979_nonce(&secret_key.secret_bytes(), msg);
let mut k = SecretKey::from_slice(&nonce).expect("RFC6979 nonce is in [1, n-1]");
let r_point = PublicKey::from_secret_key(secp, &k);
let uncompressed = r_point.serialize_uncompressed();
let r: [u8; 32] = uncompressed[1..33].try_into().expect("slice is 32 bytes");
let ry: [u8; 32] = uncompressed[33..65].try_into().expect("slice is 32 bytes");
if !is_quadratic_residue(&ry, &field) {
k = k.negate();
}
let pubkey = PublicKey::from_secret_key(secp, secret_key);
let e_bytes = challenge(&r, &pubkey, msg, &order);
let s = if e_bytes == [0u8; 32] {
k.secret_bytes()
} else {
let e_scalar = Scalar::from_be_bytes(e_bytes).ok()?;
let k_scalar = Scalar::from_be_bytes(k.secret_bytes()).ok()?;
secret_key.mul_tweak(&e_scalar).ok()?.add_tweak(&k_scalar).ok()?.secret_bytes()
};
let mut sig = [0u8; SCHNORR_SIGNATURE_SIZE];
sig[..32].copy_from_slice(&r);
sig[32..].copy_from_slice(&s);
Some(sig)
}
fn verify_inner<C: Verification>(
secp: &Secp256k1<C>,
msg: &[u8; 32],
sig: &[u8; SCHNORR_SIGNATURE_SIZE],
pubkey: &PublicKey,
) -> Option<()> {
let r: [u8; 32] = sig[..32].try_into().expect("slice is 32 bytes");
let s: [u8; 32] = sig[32..].try_into().expect("slice is 32 bytes");
let order = BigUint::from_bytes_be(&CURVE_ORDER);
let field = BigUint::from_bytes_be(&FIELD_SIZE);
let e_bytes = challenge(&r, pubkey, msg, &order);
let sg = if s == [0u8; 32] {
None
} else {
let s_scalar = Scalar::from_be_bytes(s).ok()?;
Some(generator().mul_tweak(secp, &s_scalar).ok()?)
};
let neg_ep = if e_bytes == [0u8; 32] {
None
} else {
let e_scalar = Scalar::from_be_bytes(e_bytes).ok()?;
Some(pubkey.mul_tweak(secp, &e_scalar).ok()?.negate(secp))
};
let r_point = match (sg, neg_ep) {
(None, None) => return None,
(Some(p), None) | (None, Some(p)) => p,
(Some(a), Some(b)) => a.combine(&b).ok()?,
};
let uncompressed = r_point.serialize_uncompressed();
let rx: [u8; 32] = uncompressed[1..33].try_into().expect("slice is 32 bytes");
let ry: [u8; 32] = uncompressed[33..65].try_into().expect("slice is 32 bytes");
if rx != r {
return None;
}
if is_quadratic_residue(&ry, &field) {
Some(())
} else {
None
}
}
#[cfg(test)]
mod tests {
use hex_lit::hex;
use secp256k1::Secp256k1;
use super::*;
fn pubkey() -> [u8; 33] {
hex!("030b4c866585dd868a9d62348a9cd008d6a312937048fff31670e7e920cfc7a744")
}
fn msg() -> [u8; 32] {
hex!("5255683da567900bfd3e786ed8836a4e7763c221bf1ac20ece2a5171b9199e8a")
}
fn sig() -> [u8; 64] {
hex!(
"2c56731ac2f7a7e7f11518fc7722a166b02438924ca9d8b4d111347b81d07175\
71846de67ad3d913a8fdf9d8f3f73161a4c48ae81cb183b214765feb86e255ce"
)
}
#[test]
fn verify_valid_signature() {
let secp = Secp256k1::new();
let pk = PublicKey::from_slice(&pubkey()).unwrap();
assert!(verify(&secp, &msg(), &sig(), &pk));
}
fn secret_key() -> [u8; 32] {
hex!("12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747")
}
#[test]
fn sign_matches_abc_deterministic_vector() {
let secp = Secp256k1::new();
let sk = SecretKey::from_slice(&secret_key()).unwrap();
let produced = sign(&secp, &msg(), &sk);
assert_eq!(produced, sig());
let pk = PublicKey::from_secret_key(&secp, &sk);
assert_eq!(pk.serialize(), pubkey());
assert!(verify(&secp, &msg(), &produced, &pk));
}
#[test]
fn sign_is_deterministic_and_roundtrips() {
let secp = Secp256k1::new();
for i in 1u8..=25 {
let mut sk_bytes = [0x11u8; 32];
sk_bytes[31] = i;
let sk = SecretKey::from_slice(&sk_bytes).unwrap();
let pk = PublicKey::from_secret_key(&secp, &sk);
let mut m = [0u8; 32];
m[0] = i;
m[31] = 0xab;
let s1 = sign(&secp, &m, &sk);
assert_eq!(s1, sign(&secp, &m, &sk), "i={}", i);
assert!(verify(&secp, &m, &s1, &pk), "verify i={}", i);
let mut bad = m;
bad[5] ^= 0x01;
assert!(!verify(&secp, &bad, &s1, &pk), "wrong-msg i={}", i);
}
}
#[test]
fn sign_matches_electron_cash_vectors() {
use hex::FromHex;
let secp = Secp256k1::new();
for (i, (sk_hex, msg_hex, sig_hex)) in SIGN_VECTORS.iter().enumerate() {
let sk = SecretKey::from_slice(&Vec::<u8>::from_hex(sk_hex).unwrap()).unwrap();
let m: [u8; 32] = Vec::<u8>::from_hex(msg_hex).unwrap().try_into().unwrap();
let want: [u8; 64] = Vec::<u8>::from_hex(sig_hex).unwrap().try_into().unwrap();
assert_eq!(sign(&secp, &m, &sk), want, "sign vector {}", i);
let pk = PublicKey::from_secret_key(&secp, &sk);
assert!(verify(&secp, &m, &want, &pk), "verify vector {}", i);
}
}
#[rustfmt::skip]
const SIGN_VECTORS: &[(&str, &str, &str)] = &[
("0000000000000000000000000000000000000000000000000000000001234567",
"cfc178764572d0f5eb85fcad7227d1e5e51449ba0f35323e1ad4f037842b39b1",
"65d093044a33c005626a7f86582a25dade4345eefa53ff260efdfdf27d679c933e72b1ab63226c6ebb8e7bfd4bfa8e2a3863009288eb8d01ff93ff5dfe25ce97"),
("000000000000000000000000000000000000000000000000000000009f5abf18",
"661926cbd219e433d29b279f2fcfa6e7272e26bd7b3b6169b611a8b33a6bf937",
"c5d60658112fef4e64a59b6986d11cd1a300479232f90a757e2fe4305ab5c0fe9bb6b5080d34cb7cbf5a51c36e444035c6a989c9dd8ae9d2f7aaee91eac3f34d"),
("000000000000000000000000000000000000000000000000000000013d9238c9",
"8693a153b5e69904cb5e961d45519d6f9638a1e6297266988cfc3eaa242545dc",
"159b1182a92e6ad2c09f77100c2b5e307af71fbf8228bb80e0dab3af45fa127fa07247bb3e9e489dc8d814c129a9666b8008af58b65812e97e84a0797e87ebd1"),
("00000000000000000000000000000000000000000000000000000001dbc9b27a",
"a71aac699b14d33afd3ee3db58313e076e67c43853f7562d52d977bf85512194",
"1c30ab6a99f41d6600d967f571aff310ef737186c8577ecb29f25eddd3625894131108056f26834253edcc34eb7c434da8f54558ec932bd74c0f1109e3664c48"),
("000000000000000000000000000000000000000000000000000000027a012c2b",
"feb9be5c10b2f3143f9b87586e3ac25dcc2f5acecbe20f95149aa7120f7b5d42",
"dbff3969addba5b1e8301db500bd7ffcd46e7ce2abb5aef719182904d16ddc5ae00ded5a05317570c4c530357e9878a7e9696d59355da25121298dd56e453729"),
("000000000000000000000000000000000000000000000000000000031838a5dc",
"18fb6b077b9955ab21eff5269978a19ef57ed03466b14dd4830c414eb249430c",
"78c524cfbedadff9dc5b7798e78fcb6b3d8ab66f23a74dd11c3504bf5fd7e880b642371ead2af630fb7476dba37b1fbb478782113d85dc2853502b004378c60f"),
("00000000000000000000000000000000000000000000000000000003b6701f8d",
"2a20e97b166124fc7f099e17b917f2d8c95c670f6380e412a8efad66cb780aa1",
"d8d60ecc035c2ad618abbca4fa04c6fcf4d62b593d7ac6a99130680eae7c50eb491f6df7e602351a146d2dbdc4fb5c10c441f24cfcc0fc407cf81660c2bee66b"),
("0000000000000000000000000000000000000000000000000000000454a7993e",
"cdc15f087328644d0e275f564a1d32c803da9261b08694970126f4be927b3bc3",
"2870b857cae14db02d284d9b162ae1d1e7fdfc7d71bd9f56f563fa2f42558a3a5e770ad97c39ff4b392c4fdf9211a305c674bf52fcfe752e93edf1bb06f4cc7a"),
("00000000000000000000000000000000000000000000000000000004f2df12ef",
"193d1d5db2f2ce727cd82dfa5cfc85315cc51e4e66493cf8e65b5510b49b5884",
"c7e27c74e6da07ebbf8f409b0398701c797e791df0400c65a775c8bd487ee988129c2d7dc095482e4e156bdcfd948550e01aa4725eb11005f01411d308d787f6"),
("0000000000000000000000000000000000000000000000000000000591168ca0",
"0382078a55734aea9eb6f99d14760b9b3849b1b3fe9b484f6f4bf44295f34bc9",
"d3d3fa436591eaf97aafc817f559382222d4e8806a12cc8adbb9515d652f84ebdd747c1ab8e940f60984ffdf256c2913ca212c12613a01ded15c80053e130e24"),
("000000000000000000000000000000000000000000000000000000062f4e0651",
"16b2c744f83b3729850f08b9300abc9d25c81945044cbee2d04207faac128d11",
"f9348ed9b5391c4384e537e50e4f825e6ddc1edb5e1fb2ce88e8e9cbebd742374cb756d04737a3f98ef4eced18a76e13d9fcb687349ad140bc12e073269e7bf1"),
("00000000000000000000000000000000000000000000000000000006cd858002",
"aa8bae6f8bfa3b9532877ccc3d8381768dd995a685163a037e9da8b4735a488f",
"86a2d9e120a14ee9edfdab2e3841ea31b028dcf1915cc592609fbc478b85c40439476f642310f2e359b865e32faf2a66b670fa6b4578de9a4c00c0868996c5a6"),
];
#[test]
fn reject_tampered_r_and_s() {
let secp = Secp256k1::new();
let pk = PublicKey::from_slice(&pubkey()).unwrap();
let mut bad_s = sig();
bad_s[63] ^= 0x01;
assert!(!verify(&secp, &msg(), &bad_s, &pk));
let mut bad_r = sig();
bad_r[0] ^= 0x01;
assert!(!verify(&secp, &msg(), &bad_r, &pk));
}
#[test]
fn reject_wrong_message() {
let secp = Secp256k1::new();
let pk = PublicKey::from_slice(&pubkey()).unwrap();
let mut m = msg();
m[0] ^= 0x01;
assert!(!verify(&secp, &m, &sig(), &pk));
}
#[test]
fn reject_wrong_pubkey() {
let secp = Secp256k1::new();
assert!(!verify(&secp, &msg(), &sig(), &generator()));
}
#[test]
fn degenerate_inputs_do_not_panic() {
let secp = Secp256k1::new();
let pk = PublicKey::from_slice(&pubkey()).unwrap();
assert!(!verify(&secp, &msg(), &[0u8; 64], &pk));
let mut s_eq_order = sig();
s_eq_order[32..].copy_from_slice(&CURVE_ORDER);
assert!(!verify(&secp, &msg(), &s_eq_order, &pk));
let mut r_eq_field = sig();
r_eq_field[..32].copy_from_slice(&FIELD_SIZE);
assert!(!verify(&secp, &msg(), &r_eq_field, &pk));
}
#[test]
fn signature_container_roundtrip() {
let mut bytes = sig().to_vec();
bytes.push(0x41);
let parsed = Signature::from_slice(&bytes).unwrap();
assert_eq!(parsed.sighash_type, 0x41);
assert_eq!(parsed.signature, sig());
assert_eq!(parsed.serialize().to_vec(), bytes);
assert_eq!(parsed.to_vec(), bytes);
assert_eq!(parsed.sighash_ecdsa_type(), EcdsaSighashType::All);
assert!(matches!(
Signature::from_slice(&sig()),
Err(Error::InvalidSignatureSize(64))
));
}
#[test]
fn official_and_generated_vectors() {
use hex::FromHex;
let secp = Secp256k1::new();
for (i, (pk_hex, msg_hex, sig_hex, expected)) in VECTORS.iter().enumerate() {
let pk_bytes = Vec::<u8>::from_hex(pk_hex).unwrap();
let msg: [u8; 32] = Vec::<u8>::from_hex(msg_hex).unwrap().try_into().unwrap();
let sig: [u8; 64] = Vec::<u8>::from_hex(sig_hex).unwrap().try_into().unwrap();
let pk = PublicKey::from_slice(&pk_bytes).expect("vector pubkey parses");
assert_eq!(verify(&secp, &msg, &sig, &pk), *expected, "vector index {}", i);
}
}
#[rustfmt::skip]
const VECTORS: &[(&str, &str, &str, bool)] = &[
("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"0000000000000000000000000000000000000000000000000000000000000000",
"787a848e71043d280c50470e8e1532b2dd5d20ee912a45dbdd2bd1dfbf187ef67031a98831859dc34dffeedda86831842ccd0079e1f92af177f7f22cc1dced05", true), ("02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"2a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d1e51a22ccec35599b8f266912281f8365ffc2d035a230434a1a64dc59f7013fd", true), ("03fac2114c2fbb091527eb7c64ecb11f8021cb45e8e7809d3c0938e4b8c0e5f84b",
"5e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c",
"00da9b08172a9b6f0466a2defd817f2d7ab437e0d253cb5395a963866b3574be00880371d01766935b92d2ab4cd5c8a2a5837ec57fed7660773a05f0de142380", true), ("03defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34",
"4df3c3f68fcc83b27e9d42c90431a72499f17875c81a599b566c9889b9696703",
"00000000000000000000003b78ce563f89a0ed9414f5aa28ad0d96d6795f9c6302a8dc32e64e86a333f20ef56eac9ba30b7246d6d25e22adb8c6be1aeb08d49d", true), ("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"0000000000000000000000000000000000000000000000000000000000000000",
"52818579aca59767e3291d91b76b637bef062083284992f2d95f564ca6cb4e3530b1da849c8e8304adc0cfe870660334b3cfc18e825ef1db34cfae3dfc5d8187", true), ("02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"2a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1dfa16aee06609280a19b67a24e1977e4697712b5fd2943914ecd5f730901b4ab7", false), ("03fac2114c2fbb091527eb7c64ecb11f8021cb45e8e7809d3c0938e4b8c0e5f84b",
"5e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c",
"00da9b08172a9b6f0466a2defd817f2d7ab437e0d253cb5395a963866b3574bed092f9d860f1776a1f7412ad8a1eb50daccc222bc8c0e26b2056df2f273efdec", false), ("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"0000000000000000000000000000000000000000000000000000000000000000",
"787a848e71043d280c50470e8e1532b2dd5d20ee912a45dbdd2bd1dfbf187ef68fce5677ce7a623cb20011225797ce7a8de1dc6ccd4f754a47da6c600e59543c", false), ("03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"2a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d1e51a22ccec35599b8f266912281f8365ffc2d035a230434a1a64dc59f7013fd", false), ("02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"2a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d8c3428869a663ed1e954705b020cbb3e7bb6ac31965b9ea4c73e227b17c5af5a", false), ("02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"4a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d1e51a22ccec35599b8f266912281f8365ffc2d035a230434a1a64dc59f7013fd", false), ("02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc2f1e51a22ccec35599b8f266912281f8365ffc2d035a230434a1a64dc59f7013fd", false), ("02dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"2a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1dfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", false), ("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"b5ad45dc932b245a2f88f729ce30a4a098c868bd549de8bf8d8641b37a5e8501",
"6cd434322e91d75a09228a1925dfbe80936b8242bb9da7de55493becb41bcc019919df016f8f042eb55973ae10d2a5d89d3ff40d2e00d60ba605e10713ace4ae", true), ("0252a8aebc31f9a47be775c0e7d3a6f48c89a7777306e25cb5ca3a95315492ef76",
"755857880e120d9459f8a967958f4bf832e02acdb94518de504f35c6443d818a",
"5ea4997576aa28229c049d39bb3a76d45d162f27c32c69420e691632f488b6ee536d5c79d6008f22f4a26cb11a6adffa5ca89c54a5a519359a9b3d71cfeba785", true), ("03412177b310c130bbec84b7cb5dde81731a8831a9d62ccbfed8d0033821bc012a",
"da360ee2266b1c52042a4d8735d594f85d129c90d4ddc1cc5eab4f4917872704",
"cea0d290fdf51d4e8ffac0263958dcc83433976a11fcc0d6019ee218cebd11b8b925fda6b28f64582dd527b3e786983108d10a750925a793eec041cd6cd6409a", true), ("02e463fbd8da45477a3c9e3072a481fe4ce0a4013de886c222ef286788df2d89dc",
"801d8b5450e12d2a81b26bd0bdab479dbac079fa9717d4ba0e2df9bc8d07bb1d",
"96f23fc464ab3c9bc5b9c570e227f4951a6d072f8093a8c0cbc41932762a54f07438c1b298785cc2eb0f4563aa1ae4467f3e742c107fb81ce12752b78e69021b", true), ("02340857d0d27d45304c7cb84f0ad46d03a769beaa931a1bad3709e0c4f8ec1f7b",
"82853c20b7e39fd5593a04bc3f0d2727b3a0dcf2692b92c182684ff1f9d2dc70",
"119ab9d32205d3371566dc0ba99a3aa5deda644d374874e7f2ecb42f8185f7519d06071e10a56db826453aed7d1ed5ae14293bb56235d703c60fae24e3b0c704", true), ("0205a1072be137434b913bcfe8d78c44acd86f3e0e641753cc6495e3445d600755",
"aa23a3af72f8686185a2da17fbd6bcf31879454a21c7f709c686f88378de5af1",
"af1af22289020cfa65d6ebab0bf2248276e8c77b172ef98aa8dab5ab7c7552fe549e9df00213599fa05d5da90f985bb3aafd61437e5634597dee953a418bf57d", true), ("02407adad44d938fc4db85cafeb18ecb822e11390d9bae5e3983fc025808eae001",
"98c15860ca5ebb463468135d3ccc6de5853c1d4c9a5847037f6392d1e41c4e1e",
"9637d448b90fa4bc9cf4a60636b714575bfd64855ab5272ed310b0b44d8f9339c99c840e3a1e8c726ca562cf6b2e6c880f65f07bad68de7b7f92869fe7208d70", true), ("03821a5432c90da3d74a29421bd30c0c014a8bb588a5f84991f4c91c6f27f2f5ec",
"6f1c35579bbf3607ee6f054d179b7a62f15ea684a10d85d48bc3887f95b5faaf",
"80c79ea0a1c0071dd46584d4061d004d58bc10869cbdbf9131e9729408758b97939c985c0fcc8c3929c8406617ef4f3099e7775e3663bfeda147f28b046afd90", true), ("0251e6ed965cdba91bbe88fef297d52b237ef486daeb010e601c491aabb648799a",
"3747d57a30d7f2e91a6ef5755d21da0c244d39ca91069756736dd2f9fed48089",
"7fad101c2a2b37d545cab5c5eab32ab308aadda977b9d232e1be34548171ef0007186ed6a8947cffdc534506cff0a4c3c2b65fca60b38da03af006785f3ad05d", true), ("03d702164987e8552b23a3119cbd4fefa4eb8424ea16118fbd1c8abec316abe648",
"1b315ac57eaebb5cb13bcb44d8d2d201b34b3ec358dce4841b5bf516a078fc01",
"0d378f55755d95e393cd3e9dfde2144117e8cfda6658ccf643133d821767c755d85437002e0ce081fcfc0e6bd7039d764ec24fb3692df92cea90d37b47f58c11", true), ("039afde3c2a61d0942ae1b305548753a61f1e5440eeab314ba54593555807bde2d",
"03b8a5a703e9d3f8336122e6426e45df88df237837507b0b29ce72eaa9ca3910",
"146a6b509dcff23f677534fb99ae1de09d7b98482464bc4016c8b634816e27deaa81c9a107daf4c04c7f045aefff2b1f7c7763e62d70a199171b0b2aa72e407a", true), ("0389c02529723b71a94944253c9e4ee46df421948c9be052118a5cbc880793ca99",
"7d55ae3ea7df57c7cacdad81837a8cf8c97f4ba9c440feab646f89270c63e003",
"4677c9899c13deb0b98be359e4e0299948c7bafdcb831f30e19cdd83839037af7edbeb1d08b6bf121daf8a9caad3248daefa746bc93f01ba1f6b5b343bfd19f1", true), ("023c49b03bf309764e13618e45062b660e6afce81913b3de1b2f9c7b6139c2b2e5",
"31540fa8840e8066ad99927fad063a416cf32c2b42922bd1fde75268640ba058",
"716924b0b3aff5a8686086516148646e6c69410e32f631c2c918168db4eb1322287431a66bd83b164ca34292375f5bb52b953d1f13a3e551ff60ef907476bca0", true), ("03424042e546e34c33649558739d7a46fca323e4c9b985a02cc734ed87fa7de772",
"3d0f0171e93d984e78e400fef8ab77f84c2e14780ebfb4e60b398396d1023a3b",
"d69cf9a43a3c00908f05a5cb62f4bf12bd13770435baa51e33a3fcc73ff902fbf63bfb171806d24f021038866198f58362479939967672d9cdd93914340acac2", true), ("037d291d12e258fe2127a74c7e5b0b6cfd5d776cfab61c10724d6d086c8be6935b",
"af321a1eac88adf59692e5b92d7582aadef46151de660cca6df5680d4859ff52",
"26525d0cc89027ebf394846181d6831e782c6780b61fe4a4ac277143b9ae59feddb3bd2f7409cf6026e08f3057da6c8721a95d9397b0b5bde2e66f6ceb362ca4", true), ("023d1a5340e3ae3ef97381bf8f1adcb69660bc52979caaaaa1dca5a8257af8f88b",
"3d922003e0c8ca3ff1566b8ec5a7551bb95c93942e0f03841985fe7a1b95cd06",
"07b666277b23dfa3ca0a804073c2c011d8a3d48904a24a6ca3248b4f2bb4c8f089f33e6c8547d779008d5db6cca7f4601fcb2a3e34f55bbe9520a4793a34b944", true), ("033c53db77ee32704f4144b1fa4601a612f9f122376e47264580c4ebedbf1c5141",
"52fa4ae3e0beee4f11c0b595f8339782e31bfff606ce2088e20ecf0c19fd7e87",
"fda290dde722a5edece5338fc21b38eed1a5de42e673dec7ea4c41c48a2cdd3a6dd8e924f3973c10efa88eaa793115837e9a0e95c4793602db8996fe0dbc4769", true), ("022c00bf8dff759e4159c9ec8afc180f761f6d75ca1cddfee4345949729a11bb07",
"4bb25d4438d6dd241102ea5d48944a1048c0efcb7da18645fea9ccf737729d7d",
"a7cc84c6d7521d93f45215532c82e9f5f4c39a98e2d07563d9d19aebe7cd7278e7f0cad8a1439d137f2b0ec1621b4582fd7291a3c7b9208091e1c18f5c9cdd7a", true), ("03683812db9fada8ba34ff6ddbe69013d053a8d35655182001642b47e92ceac88e",
"8829611d71c7792835ba946a92a2364991efdad492099a6435bd3f8fd5c0ebfb",
"d6f77a87008ef21836b03c826630b4a2668af6c258ec8ab56ad2fe7754f80ace46a016e5d62ae5184bb5566666fee0b1d4048f77815a87160b89a6533c82ca52", true), ("032e9e253272a3de7f90dba799c413a6712adafdf17eb77d9adbbce1dde3fbef7e",
"c4db182da4662858bac6f090a7f654bf70fbb310aeea6c0160f3244ac6b9a6f4",
"13b5aedce855a780f615b74f2aa24b98c2728f74a7262cfa8632c3f593ce8b9428473144464a03a0f854bffb30bb90e30b6529eeaa42840d6a83500791c9d85f", true), ("02bc27a6b5c3090e6615f9ab06b5ab693f06773d8a9e00fab0cd935bfccb1d0227",
"9a6330f5af36be5e155d78f1b33f22e5eac6036189efe01e6a40b6ff2a914b52",
"d514c8fc212e0425d3e016dc55faedf908067c060e9c90e5adda884643c8fd08ea4ed2506126fb1de9c0420aa408d74e544f8b2ed13084d434deb801b58f74cd", true), ("0312b95de657702f5eeaafd6e197ce14e9bef44c91031890fa9067a4511294ffb5",
"56db427db1daf1bd2017e0a147dfa2fc528566cb8572a1c9389d24215a31733a",
"da796751dcd41823ee9c2da125cef2b6b7f5a97fd5898048ea4acf3c42fd3260dfe96716e81a64b2cb70070900e1cfc91f824158f5935114c26d157c5794c3e4", true), ("0388c4a8cdf8bce52f083dc9959a1c76b7f197ceabf4a9b253bda1fab204a65560",
"6954fc0dd8cdd03b620c622384ec8a2aa8f60a3afc2ef89134b854737120db0b",
"d4d2fcb18afa1745c6a5bc53c80d123edf48a910ed5829c34e92469a61395a6a36e4bdca8492445436b3d8c2047f58f941dc39f521fe19b7638371c77a3c00b7", true), ("02cefa4c521e108f950f20ebef8df37154c3b65c5fbc22034aca28bd6a7a28b930",
"8a1502691710a0bc4e30ef9fac6b9ccfa9d58943d85bf1fc212971259e8e5ad5",
"9526a38eb78505f498e0a8a0bb4009b8da0a139ffa9b2bed565d5f1b0e1d8698b157b47b40b39b90a29ebeb7909adfac560fe556093c9f090e7db25a01c65a4b", true), ("02a73ad82896963ace01fe4b8112db2faa3116b9df06d645f259a6ba5d4f403d2f",
"156262b16d595864e3014dd14231a199c23c1b5835cfa20c0a76b6542e598d7d",
"04e16d1804a7730f168db20ffb8041874b4b4ed216a7849f791f4a5fa2802f52fced4ad967ac5303629734385dc0662cc4c3d2f8af230e30fc2ae7eb4131991a", true), ("025a8d28378a8b16af8c5999b9adbae669ffec54237905d1c48699a3ed095eeb19",
"8fff6c35874235dc06fda7662db269731bcdfd311fd3bd07d7b0abf3f55b19f2",
"9269b2d29b454434156dd2624ecf9b2565eaacfce772b66c2426769e09a6507a5dc0f3637a8c76660af6832608eb60547bd5479573d4152ae8c053cae5b620ba", true), ("03e34c08f01ca550f23f313a7f23ba54b81bf321f65d55d12708221c82553df389",
"f803e424e073b7c84bdc33b383e14c94662d4f1870b109e6bc09c1ac4c07ec59",
"81e97ca9584beee599960292c8dc37c19c22cb3cf7886da29c56728f47f014670d5d3d4f71d1b10027ea49ea3c12e3e95a01f65fa95debca481511ca74af48f3", true), ("032a5273da47b540b92f32ca84f80ba2c2659a6f1825e5194d75c6e66ca547553c",
"2874ae6b99ae79fe694d8331731bb7d1c7851ef302e3e7b91f4d62840822364c",
"d666250430c4b7813fc9c6a5656e5d3fff6a83bd4cbff636f3c852a66070bc5fc86164bce68bcd4310752f4b0b6329a23e7f044004124ebc210ec292abeda796", true), ("022ff5f93ada79e66b55d4a3205f61e00b89c749f9f8dfd73e1bc04f148433f20f",
"7ffc2343567f38a8631a269b0b2b20640a500745bb4ba64823612c5ce6dba0a7",
"3376503e746c282b8e41aa7b8e0cf1d30c74f52a48085bc4a82686e87663cc10546c9ee76babbb6a6a369c0559867d8c1b260ba3d300f9cbf9e707834e10e4ce", true), ("03b30eefe12922655f1da08a887e7f06fddf603fc52f0abc2ea51ae19f9c1ccaef",
"42d8e4cb3e4ac3f47d998723c8663af26b14948129dc44ed883c58eeb48cb796",
"65ffd568c3c0126afff4880aec5784ff8d1012049a297dc029957be39a142d42e103d8ef77e985337e074b9edcfacf3c09b39709f8094a576a0260d96f070986", true), ];
}