Move drone into its own crate
This commit is contained in:
@ -11,7 +11,10 @@ bincode = "1.0.0"
|
||||
bs58 = "0.2.0"
|
||||
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
|
||||
log = "0.4.2"
|
||||
ring = "0.13.2"
|
||||
sha2 = "0.8.0"
|
||||
serde = "1.0.27"
|
||||
serde_derive = "1.0.27"
|
||||
|
||||
serde_json = "1.0.10"
|
||||
untrusted = "0.6.2"
|
||||
|
||||
|
75
sdk/src/hash.rs
Normal file
75
sdk/src/hash.rs
Normal file
@ -0,0 +1,75 @@
|
||||
//! The `hash` module provides functions for creating SHA-256 hashes.
|
||||
|
||||
use bs58;
|
||||
use generic_array::typenum::U32;
|
||||
use generic_array::GenericArray;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Hash(GenericArray<u8, U32>);
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Hasher {
|
||||
hasher: Sha256,
|
||||
}
|
||||
|
||||
impl Hasher {
|
||||
pub fn hash(&mut self, val: &[u8]) -> () {
|
||||
self.hasher.input(val);
|
||||
}
|
||||
pub fn hashv(&mut self, vals: &[&[u8]]) -> () {
|
||||
for val in vals {
|
||||
self.hash(val);
|
||||
}
|
||||
}
|
||||
pub fn result(self) -> Hash {
|
||||
// At the time of this writing, the sha2 library is stuck on an old version
|
||||
// of generic_array (0.9.0). Decouple ourselves with a clone to our version.
|
||||
Hash(GenericArray::clone_from_slice(
|
||||
self.hasher.result().as_slice(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Hash {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Hash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Hash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash {
|
||||
pub fn new(hash_slice: &[u8]) -> Self {
|
||||
Hash(GenericArray::clone_from_slice(&hash_slice))
|
||||
}
|
||||
}
|
||||
/// Return a Sha256 hash for the given data.
|
||||
pub fn hashv(vals: &[&[u8]]) -> Hash {
|
||||
let mut hasher = Hasher::default();
|
||||
hasher.hashv(vals);
|
||||
hasher.result()
|
||||
}
|
||||
|
||||
/// Return a Sha256 hash for the given data.
|
||||
pub fn hash(val: &[u8]) -> Hash {
|
||||
hashv(&[val])
|
||||
}
|
||||
|
||||
/// Return the hash of the given hash extended with the given value.
|
||||
pub fn extend_and_hash(id: &Hash, val: &[u8]) -> Hash {
|
||||
let mut hash_data = id.as_ref().to_vec();
|
||||
hash_data.extend_from_slice(val);
|
||||
hash(&hash_data)
|
||||
}
|
@ -1,12 +1,22 @@
|
||||
pub mod account;
|
||||
pub mod hash;
|
||||
pub mod loader_instruction;
|
||||
pub mod native_program;
|
||||
pub mod packet;
|
||||
pub mod pubkey;
|
||||
pub mod signature;
|
||||
pub mod system_instruction;
|
||||
pub mod timing;
|
||||
pub mod transaction;
|
||||
|
||||
extern crate bincode;
|
||||
extern crate bs58;
|
||||
extern crate generic_array;
|
||||
extern crate log;
|
||||
extern crate ring;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate sha2;
|
||||
extern crate untrusted;
|
||||
|
2
sdk/src/packet.rs
Normal file
2
sdk/src/packet.rs
Normal file
@ -0,0 +1,2 @@
|
||||
/// Maximum over-the-wire size of a Transaction
|
||||
pub const PACKET_DATA_SIZE: usize = 512;
|
80
sdk/src/signature.rs
Normal file
80
sdk/src/signature.rs
Normal file
@ -0,0 +1,80 @@
|
||||
//! The `signature` module provides functionality for public, and private keys.
|
||||
|
||||
use bs58;
|
||||
use generic_array::typenum::U64;
|
||||
use generic_array::GenericArray;
|
||||
use pubkey::Pubkey;
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use ring::{rand, signature};
|
||||
use serde_json;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use untrusted::Input;
|
||||
|
||||
pub type Keypair = Ed25519KeyPair;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Signature(GenericArray<u8, U64>);
|
||||
|
||||
impl Signature {
|
||||
pub fn new(signature_slice: &[u8]) -> Self {
|
||||
Signature(GenericArray::clone_from_slice(&signature_slice))
|
||||
}
|
||||
|
||||
pub fn verify(&self, pubkey_bytes: &[u8], message_bytes: &[u8]) -> bool {
|
||||
let pubkey = Input::from(pubkey_bytes);
|
||||
let message = Input::from(message_bytes);
|
||||
let signature = Input::from(self.0.as_slice());
|
||||
signature::verify(&signature::ED25519, pubkey, message, signature).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Signature {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Signature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Signature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait KeypairUtil {
|
||||
fn new() -> Self;
|
||||
fn pubkey(&self) -> Pubkey;
|
||||
}
|
||||
|
||||
impl KeypairUtil for Ed25519KeyPair {
|
||||
/// Return a new ED25519 keypair
|
||||
fn new() -> Self {
|
||||
let rng = rand::SystemRandom::new();
|
||||
let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rng).expect("generate_pkcs8");
|
||||
Ed25519KeyPair::from_pkcs8(Input::from(&pkcs8_bytes)).expect("from_pcks8")
|
||||
}
|
||||
|
||||
/// Return the public key for the given keypair
|
||||
fn pubkey(&self) -> Pubkey {
|
||||
Pubkey::new(self.public_key_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_pkcs8(path: &str) -> Result<Vec<u8>, Box<error::Error>> {
|
||||
let file = File::open(path.to_string())?;
|
||||
let pkcs8: Vec<u8> = serde_json::from_reader(file)?;
|
||||
Ok(pkcs8)
|
||||
}
|
||||
|
||||
pub fn read_keypair(path: &str) -> Result<Keypair, Box<error::Error>> {
|
||||
let pkcs8 = read_pkcs8(path)?;
|
||||
let keypair = Ed25519KeyPair::from_pkcs8(Input::from(&pkcs8))?;
|
||||
Ok(keypair)
|
||||
}
|
28
sdk/src/system_instruction.rs
Normal file
28
sdk/src/system_instruction.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use pubkey::Pubkey;
|
||||
|
||||
pub const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32];
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum SystemInstruction {
|
||||
/// Create a new account
|
||||
/// * Transaction::keys[0] - source
|
||||
/// * Transaction::keys[1] - new account key
|
||||
/// * tokens - number of tokens to transfer to the new account
|
||||
/// * space - memory to allocate if greater then zero
|
||||
/// * program_id - the program id of the new account
|
||||
CreateAccount {
|
||||
tokens: u64,
|
||||
space: u64,
|
||||
program_id: Pubkey,
|
||||
},
|
||||
/// Assign account to a program
|
||||
/// * Transaction::keys[0] - account to assign
|
||||
Assign { program_id: Pubkey },
|
||||
/// Move tokens
|
||||
/// * Transaction::keys[0] - source
|
||||
/// * Transaction::keys[1] - destination
|
||||
Move { tokens: u64 },
|
||||
|
||||
/// Spawn a new program from an account
|
||||
Spawn,
|
||||
}
|
334
sdk/src/transaction.rs
Normal file
334
sdk/src/transaction.rs
Normal file
@ -0,0 +1,334 @@
|
||||
//! The `transaction` module provides functionality for creating log transactions.
|
||||
|
||||
use bincode::serialize;
|
||||
use hash::{Hash, Hasher};
|
||||
use pubkey::Pubkey;
|
||||
use serde::Serialize;
|
||||
use signature::{Keypair, KeypairUtil, Signature};
|
||||
use std::mem::size_of;
|
||||
|
||||
pub const SIG_OFFSET: usize = size_of::<u64>();
|
||||
|
||||
/// An instruction to execute a program under the `program_id` of `program_ids_index` with the
|
||||
/// specified accounts and userdata
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Instruction {
|
||||
/// The program code that executes this transaction is identified by the program_id.
|
||||
/// this is an offset into the Transaction::program_ids field
|
||||
pub program_ids_index: u8,
|
||||
/// Indices into the keys array of which accounts to load
|
||||
pub accounts: Vec<u8>,
|
||||
/// Userdata to be stored in the account
|
||||
pub userdata: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn new<T: Serialize>(program_ids_index: u8, userdata: &T, accounts: Vec<u8>) -> Self {
|
||||
let userdata = serialize(userdata).unwrap();
|
||||
Instruction {
|
||||
program_ids_index,
|
||||
userdata,
|
||||
accounts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An atomic transaction
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Transaction {
|
||||
/// A set of digital signature of `account_keys`, `program_ids`, `last_id`, `fee` and `instructions`, signed by the first
|
||||
/// signatures.len() keys of account_keys
|
||||
pub signatures: Vec<Signature>,
|
||||
|
||||
/// The `Pubkeys` that are executing this transaction userdata. The meaning of each key is
|
||||
/// program-specific.
|
||||
/// * account_keys[0] - Typically this is the `caller` public key. `signature` is verified with account_keys[0].
|
||||
/// In the future which key pays the fee and which keys have signatures would be configurable.
|
||||
/// * account_keys[1] - Typically this is the program context or the recipient of the tokens
|
||||
pub account_keys: Vec<Pubkey>,
|
||||
|
||||
/// The ID of a recent ledger entry.
|
||||
pub last_id: Hash,
|
||||
|
||||
/// The number of tokens paid for processing and storage of this transaction.
|
||||
pub fee: u64,
|
||||
|
||||
/// Keys identifying programs in the instructions vector.
|
||||
pub program_ids: Vec<Pubkey>,
|
||||
/// Programs that will be executed in sequence and commited in one atomic transaction if all
|
||||
/// succeed.
|
||||
pub instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn new<T: Serialize>(
|
||||
from_keypair: &Keypair,
|
||||
transaction_keys: &[Pubkey],
|
||||
program_id: Pubkey,
|
||||
userdata: &T,
|
||||
last_id: Hash,
|
||||
fee: u64,
|
||||
) -> Self {
|
||||
let program_ids = vec![program_id];
|
||||
let accounts = (0..=transaction_keys.len() as u8).collect();
|
||||
let instructions = vec![Instruction::new(0, userdata, accounts)];
|
||||
Self::new_with_instructions(
|
||||
&[from_keypair],
|
||||
transaction_keys,
|
||||
last_id,
|
||||
fee,
|
||||
program_ids,
|
||||
instructions,
|
||||
)
|
||||
}
|
||||
/// Create a signed transaction
|
||||
/// * `from_keypair` - The key used to sign the transaction. This key is stored as keys[0]
|
||||
/// * `account_keys` - The keys for the transaction. These are the program state
|
||||
/// instances or token recipient keys.
|
||||
/// * `last_id` - The PoH hash.
|
||||
/// * `fee` - The transaction fee.
|
||||
/// * `program_ids` - The keys that identify programs used in the `instruction` vector.
|
||||
/// * `instructions` - The programs and their arguments that the transaction will execute atomically
|
||||
pub fn new_with_instructions(
|
||||
from_keypairs: &[&Keypair],
|
||||
keys: &[Pubkey],
|
||||
last_id: Hash,
|
||||
fee: u64,
|
||||
program_ids: Vec<Pubkey>,
|
||||
instructions: Vec<Instruction>,
|
||||
) -> Self {
|
||||
let mut account_keys: Vec<_> = from_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
account_keys.extend_from_slice(keys);
|
||||
let mut tx = Transaction {
|
||||
signatures: vec![],
|
||||
account_keys,
|
||||
last_id: Hash::default(),
|
||||
fee,
|
||||
program_ids,
|
||||
instructions,
|
||||
};
|
||||
tx.sign(from_keypairs, last_id);
|
||||
tx
|
||||
}
|
||||
pub fn userdata(&self, instruction_index: usize) -> &[u8] {
|
||||
&self.instructions[instruction_index].userdata
|
||||
}
|
||||
fn key_index(&self, instruction_index: usize, accounts_index: usize) -> Option<usize> {
|
||||
self.instructions
|
||||
.get(instruction_index)
|
||||
.and_then(|instruction| instruction.accounts.get(accounts_index))
|
||||
.map(|&account_keys_index| account_keys_index as usize)
|
||||
}
|
||||
pub fn key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
|
||||
self.key_index(instruction_index, accounts_index)
|
||||
.and_then(|account_keys_index| self.account_keys.get(account_keys_index))
|
||||
}
|
||||
pub fn signed_key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
|
||||
match self.key_index(instruction_index, accounts_index) {
|
||||
None => None,
|
||||
Some(signature_index) => {
|
||||
if signature_index >= self.signatures.len() {
|
||||
return None;
|
||||
}
|
||||
self.account_keys.get(signature_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn program_id(&self, instruction_index: usize) -> &Pubkey {
|
||||
let program_ids_index = self.instructions[instruction_index].program_ids_index;
|
||||
&self.program_ids[program_ids_index as usize]
|
||||
}
|
||||
/// Get the transaction data to sign.
|
||||
pub fn get_sign_data(&self) -> Vec<u8> {
|
||||
let mut data = serialize(&self.account_keys).expect("serialize account_keys");
|
||||
|
||||
let last_id_data = serialize(&self.last_id).expect("serialize last_id");
|
||||
data.extend_from_slice(&last_id_data);
|
||||
|
||||
let fee_data = serialize(&self.fee).expect("serialize fee");
|
||||
data.extend_from_slice(&fee_data);
|
||||
|
||||
let program_ids = serialize(&self.program_ids).expect("serialize program_ids");
|
||||
data.extend_from_slice(&program_ids);
|
||||
|
||||
let instructions = serialize(&self.instructions).expect("serialize instructions");
|
||||
data.extend_from_slice(&instructions);
|
||||
data
|
||||
}
|
||||
|
||||
/// Sign this transaction.
|
||||
pub fn sign(&mut self, keypairs: &[&Keypair], last_id: Hash) {
|
||||
self.last_id = last_id;
|
||||
let sign_data = self.get_sign_data();
|
||||
self.signatures = keypairs
|
||||
.iter()
|
||||
.map(|keypair| Signature::new(&keypair.sign(&sign_data).as_ref()))
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Verify only the transaction signature.
|
||||
pub fn verify_signature(&self) -> bool {
|
||||
self.signatures
|
||||
.iter()
|
||||
.all(|s| s.verify(&self.from().as_ref(), &self.get_sign_data()))
|
||||
}
|
||||
|
||||
/// Verify that references in the instructions are valid
|
||||
pub fn verify_refs(&self) -> bool {
|
||||
for instruction in &self.instructions {
|
||||
if (instruction.program_ids_index as usize) >= self.program_ids.len() {
|
||||
return false;
|
||||
}
|
||||
for account_index in &instruction.accounts {
|
||||
if (*account_index as usize) >= self.account_keys.len() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn from(&self) -> &Pubkey {
|
||||
&self.account_keys[0]
|
||||
}
|
||||
|
||||
// a hash of a slice of transactions only needs to hash the signatures
|
||||
pub fn hash(transactions: &[Transaction]) -> Hash {
|
||||
let mut hasher = Hasher::default();
|
||||
transactions
|
||||
.iter()
|
||||
.for_each(|tx| hasher.hash(&tx.signatures[0].as_ref()));
|
||||
hasher.result()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bincode::serialize;
|
||||
|
||||
#[test]
|
||||
fn test_refs() {
|
||||
let key = Keypair::new();
|
||||
let key1 = Keypair::new().pubkey();
|
||||
let key2 = Keypair::new().pubkey();
|
||||
let prog1 = Keypair::new().pubkey();
|
||||
let prog2 = Keypair::new().pubkey();
|
||||
let instructions = vec![
|
||||
Instruction::new(0, &(), vec![0, 1]),
|
||||
Instruction::new(1, &(), vec![0, 2]),
|
||||
];
|
||||
let tx = Transaction::new_with_instructions(
|
||||
&[&key],
|
||||
&[key1, key2],
|
||||
Default::default(),
|
||||
0,
|
||||
vec![prog1, prog2],
|
||||
instructions,
|
||||
);
|
||||
assert!(tx.verify_refs());
|
||||
|
||||
assert_eq!(tx.key(0, 0), Some(&key.pubkey()));
|
||||
assert_eq!(tx.signed_key(0, 0), Some(&key.pubkey()));
|
||||
|
||||
assert_eq!(tx.key(1, 0), Some(&key.pubkey()));
|
||||
assert_eq!(tx.signed_key(1, 0), Some(&key.pubkey()));
|
||||
|
||||
assert_eq!(tx.key(0, 1), Some(&key1));
|
||||
assert_eq!(tx.signed_key(0, 1), None);
|
||||
|
||||
assert_eq!(tx.key(1, 1), Some(&key2));
|
||||
assert_eq!(tx.signed_key(1, 1), None);
|
||||
|
||||
assert_eq!(tx.key(2, 0), None);
|
||||
assert_eq!(tx.signed_key(2, 0), None);
|
||||
|
||||
assert_eq!(tx.key(0, 2), None);
|
||||
assert_eq!(tx.signed_key(0, 2), None);
|
||||
|
||||
assert_eq!(*tx.program_id(0), prog1);
|
||||
assert_eq!(*tx.program_id(1), prog2);
|
||||
}
|
||||
#[test]
|
||||
fn test_refs_invalid_program_id() {
|
||||
let key = Keypair::new();
|
||||
let instructions = vec![Instruction::new(1, &(), vec![])];
|
||||
let tx = Transaction::new_with_instructions(
|
||||
&[&key],
|
||||
&[],
|
||||
Default::default(),
|
||||
0,
|
||||
vec![],
|
||||
instructions,
|
||||
);
|
||||
assert!(!tx.verify_refs());
|
||||
}
|
||||
#[test]
|
||||
fn test_refs_invalid_account() {
|
||||
let key = Keypair::new();
|
||||
let instructions = vec![Instruction::new(0, &(), vec![1])];
|
||||
let tx = Transaction::new_with_instructions(
|
||||
&[&key],
|
||||
&[],
|
||||
Default::default(),
|
||||
0,
|
||||
vec![Default::default()],
|
||||
instructions,
|
||||
);
|
||||
assert_eq!(*tx.program_id(0), Default::default());
|
||||
assert!(!tx.verify_refs());
|
||||
}
|
||||
|
||||
/// Detect binary changes in the serialized contract userdata, which could have a downstream
|
||||
/// affect on SDKs and DApps
|
||||
#[test]
|
||||
fn test_sdk_serialize() {
|
||||
use untrusted::Input;
|
||||
let keypair = Keypair::from_pkcs8(Input::from(&[
|
||||
48, 83, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 255, 101, 36, 24, 124, 23,
|
||||
167, 21, 132, 204, 155, 5, 185, 58, 121, 75, 156, 227, 116, 193, 215, 38, 142, 22, 8,
|
||||
14, 229, 239, 119, 93, 5, 218, 161, 35, 3, 33, 0, 36, 100, 158, 252, 33, 161, 97, 185,
|
||||
62, 89, 99, 195, 250, 249, 187, 189, 171, 118, 241, 90, 248, 14, 68, 219, 231, 62, 157,
|
||||
5, 142, 27, 210, 117,
|
||||
])).expect("fu");
|
||||
let to = Pubkey::new(&[
|
||||
1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4,
|
||||
1, 1, 1,
|
||||
]);
|
||||
|
||||
let program_id = Pubkey::new(&[
|
||||
2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4,
|
||||
2, 2, 2,
|
||||
]);
|
||||
|
||||
let tx = Transaction::new(
|
||||
&keypair,
|
||||
&[keypair.pubkey(), to],
|
||||
program_id,
|
||||
&(1u8, 2u8, 3u8),
|
||||
Hash::default(),
|
||||
99,
|
||||
);
|
||||
assert_eq!(
|
||||
serialize(&tx).unwrap(),
|
||||
vec![
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 213, 248, 255, 179, 219, 217, 130, 31, 27, 85, 33, 217, 62,
|
||||
28, 180, 204, 186, 141, 178, 150, 153, 184, 205, 87, 123, 128, 101, 254, 222, 111,
|
||||
152, 17, 153, 210, 169, 1, 81, 208, 254, 64, 229, 205, 145, 10, 213, 241, 255, 31,
|
||||
184, 52, 242, 148, 213, 131, 241, 165, 144, 181, 18, 4, 58, 171, 44, 11, 3, 0, 0,
|
||||
0, 0, 0, 0, 0, 36, 100, 158, 252, 33, 161, 97, 185, 62, 89, 99, 195, 250, 249, 187,
|
||||
189, 171, 118, 241, 90, 248, 14, 68, 219, 231, 62, 157, 5, 142, 27, 210, 117, 36,
|
||||
100, 158, 252, 33, 161, 97, 185, 62, 89, 99, 195, 250, 249, 187, 189, 171, 118,
|
||||
241, 90, 248, 14, 68, 219, 231, 62, 157, 5, 142, 27, 210, 117, 1, 1, 1, 4, 5, 6, 7,
|
||||
8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 99, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user