crypto: add an ed25519 digital signature module (#13476)
parent
ff34b79d39
commit
3ac4155f0c
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 blackshirt
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
||||||
|
README
|
||||||
|
-----
|
||||||
|
|
||||||
|
This module implements `ed25519` public key digital signature algorithm for V Language ported </br>
|
||||||
|
from `Go` version of `crypto.ed25519`.
|
||||||
|
See [Ed25519](http://ed25519.cr.yp.to/) for more detail about `ed25519`.
|
|
@ -0,0 +1,181 @@
|
||||||
|
module ed25519
|
||||||
|
|
||||||
|
import crypto.rand
|
||||||
|
import crypto.sha512
|
||||||
|
import crypto.internal.subtle
|
||||||
|
import crypto.ed25519.internal.edwards25519
|
||||||
|
|
||||||
|
// public_key_size is the sizeof public keys in bytes
|
||||||
|
pub const public_key_size = 32
|
||||||
|
|
||||||
|
// private_key_size is the sizeof private keys in bytes
|
||||||
|
pub const private_key_size = 64
|
||||||
|
|
||||||
|
// signature_size is the size of signatures generated and verified by this modules, in bytes.
|
||||||
|
pub const signature_size = 64
|
||||||
|
|
||||||
|
// seed_size is the size of private key seeds in bytes
|
||||||
|
pub const seed_size = 32
|
||||||
|
|
||||||
|
// `PublicKey` is Ed25519 public keys.
|
||||||
|
pub type PublicKey = []byte
|
||||||
|
|
||||||
|
// `equal` reports whether p and x have the same value.
|
||||||
|
pub fn (p PublicKey) equal(x []byte) bool {
|
||||||
|
return subtle.constant_time_compare(p, PublicKey(x)) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// `PrivateKey` is Ed25519 private keys
|
||||||
|
pub type PrivateKey = []byte
|
||||||
|
|
||||||
|
// seed returns the private key seed corresponding to priv. RFC 8032's private keys correspond to seeds
|
||||||
|
// in this module.
|
||||||
|
pub fn (priv PrivateKey) seed() []byte {
|
||||||
|
mut seed := []byte{len: ed25519.seed_size}
|
||||||
|
copy(seed, priv[..32])
|
||||||
|
return seed
|
||||||
|
}
|
||||||
|
|
||||||
|
// `public_key` returns the []byte corresponding to priv.
|
||||||
|
pub fn (priv PrivateKey) public_key() []byte {
|
||||||
|
assert priv.len == ed25519.private_key_size
|
||||||
|
mut publickey := []byte{len: ed25519.public_key_size}
|
||||||
|
copy(publickey, priv[32..])
|
||||||
|
return PublicKey(publickey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// currentyly x not `crypto.PrivateKey`
|
||||||
|
pub fn (priv PrivateKey) equal(x []byte) bool {
|
||||||
|
return subtle.constant_time_compare(priv, PrivateKey(x)) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// `sign` signs the given message with priv.
|
||||||
|
pub fn (priv PrivateKey) sign(message []byte) ?[]byte {
|
||||||
|
/*
|
||||||
|
if opts.HashFunc() != crypto.Hash(0) {
|
||||||
|
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return sign(priv, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `sign `signs the message with privatekey and returns a signature
|
||||||
|
pub fn sign(privatekey PrivateKey, message []byte) ?[]byte {
|
||||||
|
mut signature := []byte{len: ed25519.signature_size}
|
||||||
|
sign_generic(signature, privatekey, message) ?
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_generic(signature []byte, privatekey []byte, message []byte) ? {
|
||||||
|
if privatekey.len != ed25519.private_key_size {
|
||||||
|
panic('ed25519: bad private key length: $privatekey.len')
|
||||||
|
}
|
||||||
|
seed, publickey := privatekey[..ed25519.seed_size], privatekey[ed25519.seed_size..]
|
||||||
|
|
||||||
|
mut h := sha512.sum512(seed)
|
||||||
|
mut s := edwards25519.new_scalar()
|
||||||
|
s.set_bytes_with_clamping(h[..32]) ?
|
||||||
|
mut prefix := h[32..]
|
||||||
|
|
||||||
|
mut mh := sha512.new()
|
||||||
|
mh.write(prefix) ?
|
||||||
|
mh.write(message) ?
|
||||||
|
|
||||||
|
mut msg_digest := []byte{cap: sha512.size}
|
||||||
|
msg_digest = mh.sum(msg_digest)
|
||||||
|
|
||||||
|
mut r := edwards25519.new_scalar()
|
||||||
|
r.set_uniform_bytes(msg_digest) ?
|
||||||
|
|
||||||
|
mut rr := edwards25519.Point{}
|
||||||
|
rr.scalar_base_mult(mut r)
|
||||||
|
|
||||||
|
mut kh := sha512.new()
|
||||||
|
kh.write(rr.bytes()) ?
|
||||||
|
kh.write(publickey) ?
|
||||||
|
kh.write(message) ?
|
||||||
|
|
||||||
|
mut hram_digest := []byte{cap: sha512.size}
|
||||||
|
hram_digest = kh.sum(hram_digest)
|
||||||
|
mut k := edwards25519.new_scalar()
|
||||||
|
k.set_uniform_bytes(hram_digest) ?
|
||||||
|
|
||||||
|
mut ss := edwards25519.new_scalar()
|
||||||
|
ss.multiply_add(k, s, r)
|
||||||
|
|
||||||
|
copy(signature[..32], rr.bytes())
|
||||||
|
copy(signature[32..], ss.bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// `verify` reports whether sig is a valid signature of message by publickey.
|
||||||
|
pub fn verify(publickey PublicKey, message []byte, sig []byte) ?bool {
|
||||||
|
if publickey.len != ed25519.public_key_size {
|
||||||
|
return error('ed25519: bad public key length: $publickey.len')
|
||||||
|
}
|
||||||
|
|
||||||
|
if sig.len != ed25519.signature_size || sig[63] & 224 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
mut aa := edwards25519.Point{}
|
||||||
|
aa.set_bytes(publickey) ?
|
||||||
|
|
||||||
|
mut kh := sha512.new()
|
||||||
|
kh.write(sig[..32]) ?
|
||||||
|
kh.write(publickey) ?
|
||||||
|
kh.write(message) ?
|
||||||
|
|
||||||
|
mut hram_digest := []byte{cap: sha512.size}
|
||||||
|
hram_digest = kh.sum(hram_digest)
|
||||||
|
|
||||||
|
mut k := edwards25519.new_scalar()
|
||||||
|
k.set_uniform_bytes(hram_digest) ?
|
||||||
|
|
||||||
|
mut ss := edwards25519.new_scalar()
|
||||||
|
ss.set_canonical_bytes(sig[32..]) ?
|
||||||
|
|
||||||
|
// [S]B = R + [k]A --> [k](-A) + [S]B = R
|
||||||
|
mut minus_a := edwards25519.Point{}
|
||||||
|
minus_a.negate(aa)
|
||||||
|
mut rr := edwards25519.Point{}
|
||||||
|
rr.vartime_double_scalar_base_mult(k, minus_a, ss)
|
||||||
|
|
||||||
|
return subtle.constant_time_compare(sig[..32], rr.bytes()) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// `generate_key` generates a public/private key pair entropy using `crypto.rand`.
|
||||||
|
pub fn generate_key() ?(PublicKey, PrivateKey) {
|
||||||
|
mut seed := rand.bytes(ed25519.seed_size) ?
|
||||||
|
|
||||||
|
privatekey := new_key_from_seed(seed)
|
||||||
|
publickey := []byte{len: ed25519.public_key_size}
|
||||||
|
copy(publickey, privatekey[32..])
|
||||||
|
|
||||||
|
return publickey, privatekey
|
||||||
|
}
|
||||||
|
|
||||||
|
// `new_key_from_seed` calculates a private key from a seed. private keys of RFC 8032
|
||||||
|
// correspond to seeds in this module
|
||||||
|
pub fn new_key_from_seed(seed []byte) PrivateKey {
|
||||||
|
// Outline the function body so that the returned key can be stack-allocated.
|
||||||
|
privatekey := []byte{len: ed25519.private_key_size}
|
||||||
|
new_key_from_seed_generic(privatekey, seed)
|
||||||
|
return PrivateKey(privatekey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_key_from_seed_generic(privatekey []byte, seed []byte) {
|
||||||
|
if seed.len != ed25519.seed_size {
|
||||||
|
panic('ed25519: bad seed length: $seed.len')
|
||||||
|
}
|
||||||
|
|
||||||
|
mut h := sha512.sum512(seed)
|
||||||
|
mut s := edwards25519.new_scalar()
|
||||||
|
s.set_bytes_with_clamping(h[..32]) or { panic(err.msg) }
|
||||||
|
mut aa := edwards25519.Point{}
|
||||||
|
aa.scalar_base_mult(mut s)
|
||||||
|
|
||||||
|
mut publickey := aa.bytes()
|
||||||
|
|
||||||
|
copy(privatekey, seed)
|
||||||
|
copy(privatekey[32..], publickey)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
module main
|
||||||
|
|
||||||
|
import encoding.hex
|
||||||
|
import encoding.base64
|
||||||
|
import crypto.ed25519
|
||||||
|
|
||||||
|
// adapted from https://asecuritysite.com/signatures/ed25519
|
||||||
|
fn main() {
|
||||||
|
msg := 'Hello Girl'
|
||||||
|
|
||||||
|
publ, priv := ed25519.generate_key() or { panic(err.msg) }
|
||||||
|
|
||||||
|
m := msg.bytes()
|
||||||
|
|
||||||
|
sig := ed25519.sign(priv, m) or { panic(err.msg) }
|
||||||
|
|
||||||
|
println('=== Message ===')
|
||||||
|
println('Msg: $msg \nHash: $m')
|
||||||
|
|
||||||
|
println('=== Public key ===')
|
||||||
|
println('Public key (Hex): ${hex.encode(publ)}')
|
||||||
|
println(' Public key (Base64): ${base64.encode(publ)}')
|
||||||
|
|
||||||
|
println('=== Private key ===')
|
||||||
|
println('Private key: $priv.seed().hex()') // priv[0:32]
|
||||||
|
println(' Private key (Base64): ${base64.encode(priv.seed())}') // priv[0:32]
|
||||||
|
println(' Private key (Base64) Full key: ${base64.encode(priv)}')
|
||||||
|
println(' Private key (Full key in Hex): ${hex.encode(priv)}')
|
||||||
|
|
||||||
|
println('=== signature (R,s) ===')
|
||||||
|
println('signature: R=${sig[0..32].hex()} s=${sig[32..64].hex()}')
|
||||||
|
println(' signature (Base64)=${base64.encode(sig)}')
|
||||||
|
|
||||||
|
rtn := ed25519.verify(publ, m, sig) or { panic(err.msg) }
|
||||||
|
|
||||||
|
if rtn {
|
||||||
|
println('Signature verified :$rtn')
|
||||||
|
} else {
|
||||||
|
println('signature does not verify :${!rtn}')
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
module main
|
||||||
|
|
||||||
|
// NB: this should be in vlib/crypto/ed25519/ed25519_test.v
|
||||||
|
// but is currently one folder below, because of a V parser/symbol registration bug.
|
||||||
|
// TODO: move this test back to vlib/crypto/ed25519/ed25519_test.v
|
||||||
|
import os
|
||||||
|
import sync.pool
|
||||||
|
import encoding.hex
|
||||||
|
import crypto.ed25519
|
||||||
|
|
||||||
|
const vexe = os.getenv('VEXE')
|
||||||
|
|
||||||
|
const vroot = os.dir(vexe)
|
||||||
|
|
||||||
|
const testdata = os.join_path(vroot, 'vlib/crypto/ed25519/testdata')
|
||||||
|
|
||||||
|
const contents = os.read_lines(os.join_path(testdata, 'sign.input')) or { panic(err) }
|
||||||
|
|
||||||
|
/*
|
||||||
|
struct ZeroReader {}
|
||||||
|
|
||||||
|
fn (z ZeroReader) read(mut buf []byte) ?int {
|
||||||
|
for i, _ in buf {
|
||||||
|
buf[i] = 0
|
||||||
|
}
|
||||||
|
return buf.len
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn test_sign_verify() ? {
|
||||||
|
// mut zero := ZeroReader{}
|
||||||
|
public, private := ed25519.generate_key() ?
|
||||||
|
|
||||||
|
message := 'test message'.bytes()
|
||||||
|
sig := ed25519.sign(private, message) ?
|
||||||
|
res := ed25519.verify(public, message, sig) or { false }
|
||||||
|
assert res == true
|
||||||
|
|
||||||
|
wrongmessage := 'wrong message'.bytes()
|
||||||
|
res2 := ed25519.verify(public, wrongmessage, sig) ?
|
||||||
|
assert res2 == false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_equal() ? {
|
||||||
|
public, private := ed25519.generate_key() ?
|
||||||
|
|
||||||
|
assert public.equal(public) == true
|
||||||
|
|
||||||
|
// This is not AVAILABLE
|
||||||
|
/*
|
||||||
|
if !public.Equal(crypto.Signer(private).Public()) {
|
||||||
|
t.Errorf("private.Public() is not Equal to public: %q", public)
|
||||||
|
}*/
|
||||||
|
assert private.equal(private) == true
|
||||||
|
|
||||||
|
otherpub, otherpriv := ed25519.generate_key() ?
|
||||||
|
assert public.equal(otherpub) == false
|
||||||
|
|
||||||
|
assert private.equal(otherpriv) == false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_malleability() ? {
|
||||||
|
// https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test
|
||||||
|
// that s be in [0, order). This prevents someone from adding a multiple of
|
||||||
|
// order to s and obtaining a second valid signature for the same message.
|
||||||
|
msg := [byte(0x54), 0x65, 0x73, 0x74]
|
||||||
|
sig := [byte(0x7c), 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a, 0x0f, 0x2d,
|
||||||
|
0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b, 0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a,
|
||||||
|
0x27, 0x77, 0x4a, 0xb0, 0x67, 0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f,
|
||||||
|
0x5d, 0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, 0x36, 0xa5, 0xc5,
|
||||||
|
0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d]
|
||||||
|
publickey := [byte(0x7d), 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5, 0x22,
|
||||||
|
0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34, 0xb1, 0x08, 0xc3, 0xbd, 0xae,
|
||||||
|
0x36, 0x9e, 0xf5, 0x49, 0xfa]
|
||||||
|
// verify should fail on provided bytes
|
||||||
|
res := ed25519.verify(publickey, msg, sig) or { false }
|
||||||
|
assert res == false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn works_check_on_sign_input_string(item string) bool {
|
||||||
|
// this is core part of the tests sign input
|
||||||
|
parts := item.split(':') // []string
|
||||||
|
|
||||||
|
if parts.len != 5 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// assert parts.len == 5
|
||||||
|
privbytes := hex.decode(parts[0]) or { panic(err.msg) }
|
||||||
|
pubkey := hex.decode(parts[1]) or { panic(err.msg) }
|
||||||
|
msg := hex.decode(parts[2]) or { panic(err.msg) }
|
||||||
|
mut sig := hex.decode(parts[3]) or { panic(err.msg) }
|
||||||
|
|
||||||
|
if pubkey.len != ed25519.public_key_size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// assert pubkey.len == public_key_size
|
||||||
|
|
||||||
|
sig = sig[..ed25519.signature_size]
|
||||||
|
mut priv := []byte{len: ed25519.private_key_size}
|
||||||
|
copy(priv[..], privbytes)
|
||||||
|
copy(priv[32..], pubkey)
|
||||||
|
|
||||||
|
sig2 := ed25519.sign(priv[..], msg) or { panic(err.msg) }
|
||||||
|
if sig != sig2[..] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
res := ed25519.verify(pubkey, msg, sig2) or { panic(err.msg) }
|
||||||
|
// assert res == true
|
||||||
|
if !res {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
priv2 := ed25519.new_key_from_seed(priv[..32])
|
||||||
|
if ed25519.PrivateKey(priv[..]) != priv2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey2 := priv2.public_key()
|
||||||
|
if pubkey != pubkey2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
seed2 := priv2.seed()
|
||||||
|
if priv[0..32] != seed2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn worker_for_string_content(p &pool.PoolProcessor, idx int, worker_id int) &SignResult {
|
||||||
|
item := p.get_item<string>(idx)
|
||||||
|
// println('worker_s worker_id: $worker_id | idx: $idx ')
|
||||||
|
res := works_check_on_sign_input_string(item)
|
||||||
|
mut sr := &SignResult{
|
||||||
|
item: item
|
||||||
|
result: res
|
||||||
|
}
|
||||||
|
return sr
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SignResult {
|
||||||
|
mut:
|
||||||
|
item string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test read a lot of entries in `testdata/sign.input`
|
||||||
|
// so, maybe need a long time to finish.
|
||||||
|
// be quiet and patient
|
||||||
|
fn test_input_from_djb_ed25519_crypto_sign_input_with_syncpool() ? {
|
||||||
|
// contents := os.read_lines('testdata/sign.input') or { panic(err.msg) } //[]string
|
||||||
|
mut pool_s := pool.new_pool_processor(
|
||||||
|
callback: worker_for_string_content
|
||||||
|
maxjobs: 4
|
||||||
|
)
|
||||||
|
pool_s.work_on_items<string>(contents)
|
||||||
|
for i, x in pool_s.get_results<SignResult>() {
|
||||||
|
// println("i: $i = $x.result")
|
||||||
|
assert x.result == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// same as above, but without sync.pool
|
||||||
|
/*
|
||||||
|
fn test_input_from_djb_ed25519_crypto_sign_input_without_syncpool() ? {
|
||||||
|
// contents := os.read_lines('testdata/sign.input') or { panic(err.msg) } //[]string
|
||||||
|
for i, item in ed25519.contents {
|
||||||
|
parts := item.split(':') // []string
|
||||||
|
// println(parts)
|
||||||
|
/*
|
||||||
|
if parts.len != 5 {
|
||||||
|
lg.fatal('not contains len 5')
|
||||||
|
}*/
|
||||||
|
assert parts.len == 5
|
||||||
|
privbytes := hex.decode(parts[0]) ?
|
||||||
|
pubkey := hex.decode(parts[1]) ?
|
||||||
|
msg := hex.decode(parts[2]) ?
|
||||||
|
mut sig := hex.decode(parts[3]) ?
|
||||||
|
assert pubkey.len == public_key_size
|
||||||
|
|
||||||
|
sig = sig[..signature_size]
|
||||||
|
mut priv := []byte{len: ed25519.private_key_size}
|
||||||
|
copy(priv[..], privbytes)
|
||||||
|
copy(priv[32..], pubkey)
|
||||||
|
|
||||||
|
sig2 := ed25519.sign(priv[..], msg) ?
|
||||||
|
assert sig == sig2[..]
|
||||||
|
|
||||||
|
res := ed25519.verify(pubkey, msg, sig2) ?
|
||||||
|
assert res == true
|
||||||
|
|
||||||
|
priv2 := new_key_from_seed(priv[..32])
|
||||||
|
assert priv[..] == priv2
|
||||||
|
|
||||||
|
pubkey2 := priv2.public_key()
|
||||||
|
assert pubkey == pubkey2
|
||||||
|
|
||||||
|
seed2 := priv2.seed()
|
||||||
|
assert priv[0..32] == seed2
|
||||||
|
}
|
||||||
|
}*/
|
|
@ -0,0 +1,19 @@
|
||||||
|
README
|
||||||
|
-------
|
||||||
|
|
||||||
|
This module provides arithmetic primitives operations that are useful to implement
|
||||||
|
cryptographic schemes over curve edwards25519, includes:
|
||||||
|
1. Arithmetic functions for point addition, doubling, negation, scalar multiplication
|
||||||
|
with an arbitrary point, with the base point, etc.
|
||||||
|
2. Arithmetic functions dealing with scalars modulo the prime order L of the base point.
|
||||||
|
|
||||||
|
This modules was port of Golang `edwards25519` library from [edwards25519](https://github.com/FiloSottile/edwards25519) to the V language.
|
||||||
|
|
||||||
|
|
||||||
|
About Edwards25519
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Twisted Edwards curves are a familly of elliptic curves allowing complete addition
|
||||||
|
formulas without any special case and no point at infinity.
|
||||||
|
Curve edwards25519 is based on prime 2^255 - 19 for efficient implementation.
|
||||||
|
Equation and parameters are given in RFC 7748.
|
|
@ -0,0 +1 @@
|
||||||
|
module edwards25519
|
|
@ -0,0 +1,733 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
import math.bits
|
||||||
|
import math.unsigned
|
||||||
|
import encoding.binary
|
||||||
|
import crypto.internal.subtle
|
||||||
|
|
||||||
|
// embedded unsigned.Uint128
|
||||||
|
struct Uint128 {
|
||||||
|
unsigned.Uint128
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element represents an element of the edwards25519 GF(2^255-19). Note that this
|
||||||
|
// is not a cryptographically secure group, and should only be used to interact
|
||||||
|
// with edwards25519.Point coordinates.
|
||||||
|
//
|
||||||
|
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||||
|
// are allowed to alias.
|
||||||
|
//
|
||||||
|
// The zero value is a valid zero element.
|
||||||
|
struct Element {
|
||||||
|
mut:
|
||||||
|
// An element t represents the integer
|
||||||
|
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
|
||||||
|
//
|
||||||
|
// Between operations, all limbs are expected to be lower than 2^52.
|
||||||
|
l0 u64
|
||||||
|
l1 u64
|
||||||
|
l2 u64
|
||||||
|
l3 u64
|
||||||
|
l4 u64
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
mask_low_51_bits = u64((1 << 51) - 1)
|
||||||
|
fe_zero = Element{
|
||||||
|
l0: 0
|
||||||
|
l1: 0
|
||||||
|
l2: 0
|
||||||
|
l3: 0
|
||||||
|
l4: 0
|
||||||
|
}
|
||||||
|
fe_one = Element{
|
||||||
|
l0: 1
|
||||||
|
l1: 0
|
||||||
|
l2: 0
|
||||||
|
l3: 0
|
||||||
|
l4: 0
|
||||||
|
}
|
||||||
|
// sqrt_m1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
|
||||||
|
sqrt_m1 = Element{
|
||||||
|
l0: 1718705420411056
|
||||||
|
l1: 234908883556509
|
||||||
|
l2: 2233514472574048
|
||||||
|
l3: 2117202627021982
|
||||||
|
l4: 765476049583133
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// mul_64 returns a * b.
|
||||||
|
fn mul_64(a u64, b u64) Uint128 {
|
||||||
|
hi, lo := bits.mul_64(a, b)
|
||||||
|
return Uint128{
|
||||||
|
lo: lo
|
||||||
|
hi: hi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add_mul_64 returns v + a * b.
|
||||||
|
fn add_mul_64(v Uint128, a u64, b u64) Uint128 {
|
||||||
|
mut hi, lo := bits.mul_64(a, b)
|
||||||
|
low, carry := bits.add_64(lo, v.lo, 0)
|
||||||
|
hi, _ = bits.add_64(hi, v.hi, carry)
|
||||||
|
return Uint128{
|
||||||
|
lo: low
|
||||||
|
hi: hi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift_right_by_51 returns a >> 51. a is assumed to be at most 115 bits.
|
||||||
|
fn shift_right_by_51(a Uint128) u64 {
|
||||||
|
return (a.hi << (64 - 51)) | (a.lo >> 51)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fe_mul_generic(a Element, b Element) Element {
|
||||||
|
a0 := a.l0
|
||||||
|
a1 := a.l1
|
||||||
|
a2 := a.l2
|
||||||
|
a3 := a.l3
|
||||||
|
a4 := a.l4
|
||||||
|
|
||||||
|
b0 := b.l0
|
||||||
|
b1 := b.l1
|
||||||
|
b2 := b.l2
|
||||||
|
b3 := b.l3
|
||||||
|
b4 := b.l4
|
||||||
|
|
||||||
|
// Limb multiplication works like pen-and-paper columnar multiplication, but
|
||||||
|
// with 51-bit limbs instead of digits.
|
||||||
|
//
|
||||||
|
// a4 a3 a2 a1 a0 x
|
||||||
|
// b4 b3 b2 b1 b0 =
|
||||||
|
// ------------------------
|
||||||
|
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||||
|
// a4b1 a3b1 a2b1 a1b1 a0b1 +
|
||||||
|
// a4b2 a3b2 a2b2 a1b2 a0b2 +
|
||||||
|
// a4b3 a3b3 a2b3 a1b3 a0b3 +
|
||||||
|
// a4b4 a3b4 a2b4 a1b4 a0b4 =
|
||||||
|
// ----------------------------------------------
|
||||||
|
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
|
||||||
|
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
|
||||||
|
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
|
||||||
|
//
|
||||||
|
// Reduction can be carried out simultaneously to multiplication. For
|
||||||
|
// example, we do not compute r5: whenever the result of a multiplication
|
||||||
|
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
|
||||||
|
//
|
||||||
|
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||||
|
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
|
||||||
|
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
|
||||||
|
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
|
||||||
|
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
|
||||||
|
// --------------------------------------
|
||||||
|
// r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// Finally we add up the columns into wide, overlapping limbs.
|
||||||
|
|
||||||
|
a1_19 := a1 * 19
|
||||||
|
a2_19 := a2 * 19
|
||||||
|
a3_19 := a3 * 19
|
||||||
|
a4_19 := a4 * 19
|
||||||
|
|
||||||
|
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||||
|
mut r0 := mul_64(a0, b0)
|
||||||
|
r0 = add_mul_64(r0, a1_19, b4)
|
||||||
|
r0 = add_mul_64(r0, a2_19, b3)
|
||||||
|
r0 = add_mul_64(r0, a3_19, b2)
|
||||||
|
r0 = add_mul_64(r0, a4_19, b1)
|
||||||
|
|
||||||
|
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
|
||||||
|
mut r1 := mul_64(a0, b1)
|
||||||
|
r1 = add_mul_64(r1, a1, b0)
|
||||||
|
r1 = add_mul_64(r1, a2_19, b4)
|
||||||
|
r1 = add_mul_64(r1, a3_19, b3)
|
||||||
|
r1 = add_mul_64(r1, a4_19, b2)
|
||||||
|
|
||||||
|
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
|
||||||
|
mut r2 := mul_64(a0, b2)
|
||||||
|
r2 = add_mul_64(r2, a1, b1)
|
||||||
|
r2 = add_mul_64(r2, a2, b0)
|
||||||
|
r2 = add_mul_64(r2, a3_19, b4)
|
||||||
|
r2 = add_mul_64(r2, a4_19, b3)
|
||||||
|
|
||||||
|
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
|
||||||
|
mut r3 := mul_64(a0, b3)
|
||||||
|
r3 = add_mul_64(r3, a1, b2)
|
||||||
|
r3 = add_mul_64(r3, a2, b1)
|
||||||
|
r3 = add_mul_64(r3, a3, b0)
|
||||||
|
r3 = add_mul_64(r3, a4_19, b4)
|
||||||
|
|
||||||
|
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||||
|
mut r4 := mul_64(a0, b4)
|
||||||
|
r4 = add_mul_64(r4, a1, b3)
|
||||||
|
r4 = add_mul_64(r4, a2, b2)
|
||||||
|
r4 = add_mul_64(r4, a3, b1)
|
||||||
|
r4 = add_mul_64(r4, a4, b0)
|
||||||
|
|
||||||
|
// After the multiplication, we need to reduce (carry) the five coefficients
|
||||||
|
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
|
||||||
|
// to respect the Element invariant.
|
||||||
|
//
|
||||||
|
// Overall, the reduction works the same as carryPropagate, except with
|
||||||
|
// wider inputs: we take the carry for each coefficient by shifting it right
|
||||||
|
// by 51, and add it to the limb above it. The top carry is multiplied by 19
|
||||||
|
// according to the reduction identity and added to the lowest limb.
|
||||||
|
//
|
||||||
|
// The largest coefficient (r0) will be at most 111 bits, which guarantees
|
||||||
|
// that all carries are at most 111 - 51 = 60 bits, which fits in a u64.
|
||||||
|
//
|
||||||
|
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||||
|
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
|
||||||
|
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
|
||||||
|
// r0 < 2⁷ × 2⁵² × 2⁵²
|
||||||
|
// r0 < 2¹¹¹
|
||||||
|
//
|
||||||
|
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
|
||||||
|
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a u64 and
|
||||||
|
// allows us to easily apply the reduction identity.
|
||||||
|
//
|
||||||
|
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||||
|
// r4 < 5 × 2⁵² × 2⁵²
|
||||||
|
// r4 < 2¹⁰⁷
|
||||||
|
//
|
||||||
|
|
||||||
|
c0 := shift_right_by_51(r0)
|
||||||
|
c1 := shift_right_by_51(r1)
|
||||||
|
c2 := shift_right_by_51(r2)
|
||||||
|
c3 := shift_right_by_51(r3)
|
||||||
|
c4 := shift_right_by_51(r4)
|
||||||
|
|
||||||
|
rr0 := r0.lo & edwards25519.mask_low_51_bits + c4 * 19
|
||||||
|
rr1 := r1.lo & edwards25519.mask_low_51_bits + c0
|
||||||
|
rr2 := r2.lo & edwards25519.mask_low_51_bits + c1
|
||||||
|
rr3 := r3.lo & edwards25519.mask_low_51_bits + c2
|
||||||
|
rr4 := r4.lo & edwards25519.mask_low_51_bits + c3
|
||||||
|
|
||||||
|
// Now all coefficients fit into 64-bit registers but are still too large to
|
||||||
|
// be passed around as a Element. We therefore do one last carry chain,
|
||||||
|
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
|
||||||
|
mut v := Element{
|
||||||
|
l0: rr0
|
||||||
|
l1: rr1
|
||||||
|
l2: rr2
|
||||||
|
l3: rr3
|
||||||
|
l4: rr4
|
||||||
|
}
|
||||||
|
// v.carryPropagate()
|
||||||
|
// using `carry_propagate_generic()` instead
|
||||||
|
v = v.carry_propagate_generic()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// carryPropagate brings the limbs below 52 bits by applying the reduction
|
||||||
|
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry.
|
||||||
|
fn (mut v Element) carry_propagate_generic() Element {
|
||||||
|
c0 := v.l0 >> 51
|
||||||
|
c1 := v.l1 >> 51
|
||||||
|
c2 := v.l2 >> 51
|
||||||
|
c3 := v.l3 >> 51
|
||||||
|
c4 := v.l4 >> 51
|
||||||
|
|
||||||
|
v.l0 = v.l0 & edwards25519.mask_low_51_bits + c4 * 19
|
||||||
|
v.l1 = v.l1 & edwards25519.mask_low_51_bits + c0
|
||||||
|
v.l2 = v.l2 & edwards25519.mask_low_51_bits + c1
|
||||||
|
v.l3 = v.l3 & edwards25519.mask_low_51_bits + c2
|
||||||
|
v.l4 = v.l4 & edwards25519.mask_low_51_bits + c3
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fe_square_generic(a Element) Element {
|
||||||
|
l0 := a.l0
|
||||||
|
l1 := a.l1
|
||||||
|
l2 := a.l2
|
||||||
|
l3 := a.l3
|
||||||
|
l4 := a.l4
|
||||||
|
|
||||||
|
// Squaring works precisely like multiplication above, but thanks to its
|
||||||
|
// symmetry we get to group a few terms together.
|
||||||
|
//
|
||||||
|
// l4 l3 l2 l1 l0 x
|
||||||
|
// l4 l3 l2 l1 l0 =
|
||||||
|
// ------------------------
|
||||||
|
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||||
|
// l4l1 l3l1 l2l1 l1l1 l0l1 +
|
||||||
|
// l4l2 l3l2 l2l2 l1l2 l0l2 +
|
||||||
|
// l4l3 l3l3 l2l3 l1l3 l0l3 +
|
||||||
|
// l4l4 l3l4 l2l4 l1l4 l0l4 =
|
||||||
|
// ----------------------------------------------
|
||||||
|
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||||
|
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
|
||||||
|
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
|
||||||
|
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
|
||||||
|
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
|
||||||
|
// --------------------------------------
|
||||||
|
// r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
|
||||||
|
// only three mul_64 and four Add64, instead of five and eight.
|
||||||
|
|
||||||
|
l0_2 := l0 * 2
|
||||||
|
l1_2 := l1 * 2
|
||||||
|
|
||||||
|
l1_38 := l1 * 38
|
||||||
|
l2_38 := l2 * 38
|
||||||
|
l3_38 := l3 * 38
|
||||||
|
|
||||||
|
l3_19 := l3 * 19
|
||||||
|
l4_19 := l4 * 19
|
||||||
|
|
||||||
|
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
|
||||||
|
mut r0 := mul_64(l0, l0)
|
||||||
|
r0 = add_mul_64(r0, l1_38, l4)
|
||||||
|
r0 = add_mul_64(r0, l2_38, l3)
|
||||||
|
|
||||||
|
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
|
||||||
|
mut r1 := mul_64(l0_2, l1)
|
||||||
|
r1 = add_mul_64(r1, l2_38, l4)
|
||||||
|
r1 = add_mul_64(r1, l3_19, l3)
|
||||||
|
|
||||||
|
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
|
||||||
|
mut r2 := mul_64(l0_2, l2)
|
||||||
|
r2 = add_mul_64(r2, l1, l1)
|
||||||
|
r2 = add_mul_64(r2, l3_38, l4)
|
||||||
|
|
||||||
|
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
|
||||||
|
mut r3 := mul_64(l0_2, l3)
|
||||||
|
r3 = add_mul_64(r3, l1_2, l2)
|
||||||
|
r3 = add_mul_64(r3, l4_19, l4)
|
||||||
|
|
||||||
|
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
|
||||||
|
mut r4 := mul_64(l0_2, l4)
|
||||||
|
r4 = add_mul_64(r4, l1_2, l3)
|
||||||
|
r4 = add_mul_64(r4, l2, l2)
|
||||||
|
|
||||||
|
c0 := shift_right_by_51(r0)
|
||||||
|
c1 := shift_right_by_51(r1)
|
||||||
|
c2 := shift_right_by_51(r2)
|
||||||
|
c3 := shift_right_by_51(r3)
|
||||||
|
c4 := shift_right_by_51(r4)
|
||||||
|
|
||||||
|
rr0 := r0.lo & edwards25519.mask_low_51_bits + c4 * 19
|
||||||
|
rr1 := r1.lo & edwards25519.mask_low_51_bits + c0
|
||||||
|
rr2 := r2.lo & edwards25519.mask_low_51_bits + c1
|
||||||
|
rr3 := r3.lo & edwards25519.mask_low_51_bits + c2
|
||||||
|
rr4 := r4.lo & edwards25519.mask_low_51_bits + c3
|
||||||
|
|
||||||
|
mut v := Element{
|
||||||
|
l0: rr0
|
||||||
|
l1: rr1
|
||||||
|
l2: rr2
|
||||||
|
l3: rr3
|
||||||
|
l4: rr4
|
||||||
|
}
|
||||||
|
v = v.carry_propagate_generic()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero sets v = 0, and returns v.
|
||||||
|
fn (mut v Element) zero() Element {
|
||||||
|
v = edwards25519.fe_zero
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// one sets v = 1, and returns v.
|
||||||
|
fn (mut v Element) one() Element {
|
||||||
|
v = edwards25519.fe_one
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduce reduces v modulo 2^255 - 19 and returns it.
|
||||||
|
fn (mut v Element) reduce() Element {
|
||||||
|
v = v.carry_propagate_generic()
|
||||||
|
|
||||||
|
// After the light reduction we now have a edwards25519 element representation
|
||||||
|
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
|
||||||
|
|
||||||
|
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
|
||||||
|
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
|
||||||
|
mut c := (v.l0 + 19) >> 51
|
||||||
|
c = (v.l1 + c) >> 51
|
||||||
|
c = (v.l2 + c) >> 51
|
||||||
|
c = (v.l3 + c) >> 51
|
||||||
|
c = (v.l4 + c) >> 51
|
||||||
|
|
||||||
|
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
|
||||||
|
// effectively applying the reduction identity to the carry.
|
||||||
|
v.l0 += 19 * c
|
||||||
|
|
||||||
|
v.l1 += v.l0 >> 51
|
||||||
|
v.l0 = v.l0 & edwards25519.mask_low_51_bits
|
||||||
|
v.l2 += v.l1 >> 51
|
||||||
|
v.l1 = v.l1 & edwards25519.mask_low_51_bits
|
||||||
|
v.l3 += v.l2 >> 51
|
||||||
|
v.l2 = v.l2 & edwards25519.mask_low_51_bits
|
||||||
|
v.l4 += v.l3 >> 51
|
||||||
|
v.l3 = v.l3 & edwards25519.mask_low_51_bits
|
||||||
|
// no additional carry
|
||||||
|
v.l4 = v.l4 & edwards25519.mask_low_51_bits
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sets v = a + b, and returns v.
|
||||||
|
fn (mut v Element) add(a Element, b Element) Element {
|
||||||
|
v.l0 = a.l0 + b.l0
|
||||||
|
v.l1 = a.l1 + b.l1
|
||||||
|
v.l2 = a.l2 + b.l2
|
||||||
|
v.l3 = a.l3 + b.l3
|
||||||
|
v.l4 = a.l4 + b.l4
|
||||||
|
// Using the generic implementation here is actually faster than the
|
||||||
|
// assembly. Probably because the body of this function is so simple that
|
||||||
|
// the compiler can figure out better optimizations by inlining the carry
|
||||||
|
// propagation.
|
||||||
|
return v.carry_propagate_generic()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract sets v = a - b, and returns v.
|
||||||
|
fn (mut v Element) subtract(a Element, b Element) Element {
|
||||||
|
// We first add 2 * p, to guarantee the subtraction won't underflow, and
|
||||||
|
// then subtract b (which can be up to 2^255 + 2^13 * 19).
|
||||||
|
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
|
||||||
|
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
|
||||||
|
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
|
||||||
|
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
|
||||||
|
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
|
||||||
|
return v.carry_propagate_generic()
|
||||||
|
}
|
||||||
|
|
||||||
|
// `negate` sets v = -a, and returns v.
|
||||||
|
fn (mut v Element) negate(a Element) Element {
|
||||||
|
return v.subtract(edwards25519.fe_zero, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invert sets v = 1/z mod p, and returns v.
|
||||||
|
//
|
||||||
|
// If z == 0, invert returns v = 0.
|
||||||
|
fn (mut v Element) invert(z Element) Element {
|
||||||
|
// Inversion is implemented as exponentiation with exponent p − 2. It uses the
|
||||||
|
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
|
||||||
|
mut z2 := Element{}
|
||||||
|
mut z9 := Element{}
|
||||||
|
mut z11 := Element{}
|
||||||
|
mut z2_5_0 := Element{}
|
||||||
|
mut z2_10_0 := Element{}
|
||||||
|
mut z2_20_0 := Element{}
|
||||||
|
mut z2_50_0 := Element{}
|
||||||
|
mut z2_100_0 := Element{}
|
||||||
|
mut t := Element{}
|
||||||
|
|
||||||
|
z2.square(z) // 2
|
||||||
|
t.square(z2) // 4
|
||||||
|
t.square(t) // 8
|
||||||
|
z9.multiply(t, z) // 9
|
||||||
|
z11.multiply(z9, z2) // 11
|
||||||
|
t.square(z11) // 22
|
||||||
|
z2_5_0.multiply(t, z9) // 31 = 2^5 - 2^0
|
||||||
|
|
||||||
|
t.square(z2_5_0) // 2^6 - 2^1
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
t.square(t) // 2^10 - 2^5
|
||||||
|
}
|
||||||
|
z2_10_0.multiply(t, z2_5_0) // 2^10 - 2^0
|
||||||
|
|
||||||
|
t.square(z2_10_0) // 2^11 - 2^1
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
t.square(t) // 2^20 - 2^10
|
||||||
|
}
|
||||||
|
z2_20_0.multiply(t, z2_10_0) // 2^20 - 2^0
|
||||||
|
|
||||||
|
t.square(z2_20_0) // 2^21 - 2^1
|
||||||
|
for i := 0; i < 19; i++ {
|
||||||
|
t.square(t) // 2^40 - 2^20
|
||||||
|
}
|
||||||
|
t.multiply(t, z2_20_0) // 2^40 - 2^0
|
||||||
|
|
||||||
|
t.square(t) // 2^41 - 2^1
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
t.square(t) // 2^50 - 2^10
|
||||||
|
}
|
||||||
|
z2_50_0.multiply(t, z2_10_0) // 2^50 - 2^0
|
||||||
|
|
||||||
|
t.square(z2_50_0) // 2^51 - 2^1
|
||||||
|
for i := 0; i < 49; i++ {
|
||||||
|
t.square(t) // 2^100 - 2^50
|
||||||
|
}
|
||||||
|
z2_100_0.multiply(t, z2_50_0) // 2^100 - 2^0
|
||||||
|
|
||||||
|
t.square(z2_100_0) // 2^101 - 2^1
|
||||||
|
for i := 0; i < 99; i++ {
|
||||||
|
t.square(t) // 2^200 - 2^100
|
||||||
|
}
|
||||||
|
t.multiply(t, z2_100_0) // 2^200 - 2^0
|
||||||
|
|
||||||
|
t.square(t) // 2^201 - 2^1
|
||||||
|
for i := 0; i < 49; i++ {
|
||||||
|
t.square(t) // 2^250 - 2^50
|
||||||
|
}
|
||||||
|
t.multiply(t, z2_50_0) // 2^250 - 2^0
|
||||||
|
|
||||||
|
t.square(t) // 2^251 - 2^1
|
||||||
|
t.square(t) // 2^252 - 2^2
|
||||||
|
t.square(t) // 2^253 - 2^3
|
||||||
|
t.square(t) // 2^254 - 2^4
|
||||||
|
t.square(t) // 2^255 - 2^5
|
||||||
|
|
||||||
|
return v.multiply(t, z11) // 2^255 - 21
|
||||||
|
}
|
||||||
|
|
||||||
|
// square sets v = x * x, and returns v.
|
||||||
|
fn (mut v Element) square(x Element) Element {
|
||||||
|
v = fe_square_generic(x)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiply sets v = x * y, and returns v.
|
||||||
|
fn (mut v Element) multiply(x Element, y Element) Element {
|
||||||
|
v = fe_mul_generic(x, y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul_51 returns lo + hi * 2⁵¹ = a * b.
|
||||||
|
fn mul_51(a u64, b u32) (u64, u64) {
|
||||||
|
mh, ml := bits.mul_64(a, u64(b))
|
||||||
|
lo := ml & edwards25519.mask_low_51_bits
|
||||||
|
hi := (mh << 13) | (ml >> 51)
|
||||||
|
return lo, hi
|
||||||
|
}
|
||||||
|
|
||||||
|
// pow_22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
|
||||||
|
fn (mut v Element) pow_22523(x Element) Element {
|
||||||
|
mut t0, mut t1, mut t2 := Element{}, Element{}, Element{}
|
||||||
|
|
||||||
|
t0.square(x) // x^2
|
||||||
|
t1.square(t0) // x^4
|
||||||
|
t1.square(t1) // x^8
|
||||||
|
t1.multiply(x, t1) // x^9
|
||||||
|
t0.multiply(t0, t1) // x^11
|
||||||
|
t0.square(t0) // x^22
|
||||||
|
t0.multiply(t1, t0) // x^31
|
||||||
|
t1.square(t0) // x^62
|
||||||
|
for i := 1; i < 5; i++ { // x^992
|
||||||
|
t1.square(t1)
|
||||||
|
}
|
||||||
|
t0.multiply(t1, t0) // x^1023 -> 1023 = 2^10 - 1
|
||||||
|
t1.square(t0) // 2^11 - 2
|
||||||
|
for i := 1; i < 10; i++ { // 2^20 - 2^10
|
||||||
|
t1.square(t1)
|
||||||
|
}
|
||||||
|
t1.multiply(t1, t0) // 2^20 - 1
|
||||||
|
t2.square(t1) // 2^21 - 2
|
||||||
|
for i := 1; i < 20; i++ { // 2^40 - 2^20
|
||||||
|
t2.square(t2)
|
||||||
|
}
|
||||||
|
t1.multiply(t2, t1) // 2^40 - 1
|
||||||
|
t1.square(t1) // 2^41 - 2
|
||||||
|
for i := 1; i < 10; i++ { // 2^50 - 2^10
|
||||||
|
t1.square(t1)
|
||||||
|
}
|
||||||
|
t0.multiply(t1, t0) // 2^50 - 1
|
||||||
|
t1.square(t0) // 2^51 - 2
|
||||||
|
for i := 1; i < 50; i++ { // 2^100 - 2^50
|
||||||
|
t1.square(t1)
|
||||||
|
}
|
||||||
|
t1.multiply(t1, t0) // 2^100 - 1
|
||||||
|
t2.square(t1) // 2^101 - 2
|
||||||
|
for i := 1; i < 100; i++ { // 2^200 - 2^100
|
||||||
|
t2.square(t2)
|
||||||
|
}
|
||||||
|
t1.multiply(t2, &t1) // 2^200 - 1
|
||||||
|
t1.square(t1) // 2^201 - 2
|
||||||
|
for i := 1; i < 50; i++ { // 2^250 - 2^50
|
||||||
|
t1.square(t1)
|
||||||
|
}
|
||||||
|
t0.multiply(t1, t0) // 2^250 - 1
|
||||||
|
t0.square(t0) // 2^251 - 2
|
||||||
|
t0.square(t0) // 2^252 - 4
|
||||||
|
return v.multiply(t0, x) // 2^252 - 3 -> x^(2^252-3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sqrt_ratio sets r to the non-negative square root of the ratio of u and v.
|
||||||
|
//
|
||||||
|
// If u/v is square, sqrt_ratio returns r and 1. If u/v is not square, sqrt_ratio
|
||||||
|
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
|
||||||
|
// and returns r and 0.
|
||||||
|
fn (mut r Element) sqrt_ratio(u Element, v Element) (Element, int) {
|
||||||
|
mut a, mut b := Element{}, Element{}
|
||||||
|
|
||||||
|
// r = (u * v3) * (u * v7)^((p-5)/8)
|
||||||
|
v2 := a.square(v)
|
||||||
|
uv3 := b.multiply(u, b.multiply(v2, v))
|
||||||
|
uv7 := a.multiply(uv3, a.square(v2))
|
||||||
|
r.multiply(uv3, r.pow_22523(uv7))
|
||||||
|
|
||||||
|
mut check := a.multiply(v, a.square(r)) // check = v * r^2
|
||||||
|
|
||||||
|
mut uneg := b.negate(u)
|
||||||
|
correct_sign_sqrt := check.equal(u)
|
||||||
|
flipped_sign_sqrt := check.equal(uneg)
|
||||||
|
flipped_sign_sqrt_i := check.equal(uneg.multiply(uneg, edwards25519.sqrt_m1))
|
||||||
|
|
||||||
|
rprime := b.multiply(r, edwards25519.sqrt_m1) // r_prime = SQRT_M1 * r
|
||||||
|
// r = CT_selected(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
|
||||||
|
r.selected(rprime, r, flipped_sign_sqrt | flipped_sign_sqrt_i)
|
||||||
|
|
||||||
|
r.absolute(r) // Choose the nonnegative square root.
|
||||||
|
return r, correct_sign_sqrt | flipped_sign_sqrt
|
||||||
|
}
|
||||||
|
|
||||||
|
// mask_64_bits returns 0xffffffff if cond is 1, and 0 otherwise.
|
||||||
|
fn mask_64_bits(cond int) u64 {
|
||||||
|
// in go, `^` operates on bit mean NOT, flip bit
|
||||||
|
// in v, its a ~ bitwise NOT
|
||||||
|
return ~(u64(cond) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// selected sets v to a if cond == 1, and to b if cond == 0.
|
||||||
|
fn (mut v Element) selected(a Element, b Element, cond int) Element {
|
||||||
|
// see above notes
|
||||||
|
m := mask_64_bits(cond)
|
||||||
|
v.l0 = (m & a.l0) | (~m & b.l0)
|
||||||
|
v.l1 = (m & a.l1) | (~m & b.l1)
|
||||||
|
v.l2 = (m & a.l2) | (~m & b.l2)
|
||||||
|
v.l3 = (m & a.l3) | (~m & b.l3)
|
||||||
|
v.l4 = (m & a.l4) | (~m & b.l4)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// is_negative returns 1 if v is negative, and 0 otherwise.
|
||||||
|
fn (mut v Element) is_negative() int {
|
||||||
|
return int(v.bytes()[0] & 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// absolute sets v to |u|, and returns v.
|
||||||
|
fn (mut v Element) absolute(u Element) Element {
|
||||||
|
mut e := Element{}
|
||||||
|
mut uk := u
|
||||||
|
return v.selected(e.negate(uk), uk, uk.is_negative())
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sets v = a, and returns v.
|
||||||
|
fn (mut v Element) set(a Element) Element {
|
||||||
|
v = a
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// set_bytes sets v to x, where x is a 32-byte little-endian encoding. If x is
|
||||||
|
// not of the right length, SetUniformBytes returns nil and an error, and the
|
||||||
|
// receiver is unchanged.
|
||||||
|
//
|
||||||
|
// Consistent with RFC 7748, the most significant bit (the high bit of the
|
||||||
|
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
|
||||||
|
// are accepted. Note that this is laxer than specified by RFC 8032.
|
||||||
|
fn (mut v Element) set_bytes(x []byte) ?Element {
|
||||||
|
if x.len != 32 {
|
||||||
|
return error('edwards25519: invalid edwards25519 element input size')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
|
||||||
|
v.l0 = binary.little_endian_u64(x[0..8])
|
||||||
|
v.l0 &= edwards25519.mask_low_51_bits
|
||||||
|
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
|
||||||
|
v.l1 = binary.little_endian_u64(x[6..14]) >> 3
|
||||||
|
v.l1 &= edwards25519.mask_low_51_bits
|
||||||
|
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
|
||||||
|
v.l2 = binary.little_endian_u64(x[12..20]) >> 6
|
||||||
|
v.l2 &= edwards25519.mask_low_51_bits
|
||||||
|
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
|
||||||
|
v.l3 = binary.little_endian_u64(x[19..27]) >> 1
|
||||||
|
v.l3 &= edwards25519.mask_low_51_bits
|
||||||
|
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
|
||||||
|
// Note: not bytes 25:33, shift 4, to avoid overread.
|
||||||
|
v.l4 = binary.little_endian_u64(x[24..32]) >> 12
|
||||||
|
v.l4 &= edwards25519.mask_low_51_bits
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytes returns the canonical 32-byte little-endian encoding of v.
|
||||||
|
pub fn (mut v Element) bytes() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
// out := v.bytes_generic()
|
||||||
|
return v.bytes_generic()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Element) bytes_generic() []byte {
|
||||||
|
mut out := []byte{len: 32}
|
||||||
|
|
||||||
|
v = v.reduce()
|
||||||
|
|
||||||
|
mut buf := []byte{len: 8}
|
||||||
|
idxs := [v.l0, v.l1, v.l2, v.l3, v.l4]
|
||||||
|
for i, l in idxs {
|
||||||
|
bits_offset := i * 51
|
||||||
|
binary.little_endian_put_u64(mut buf, l << u32(bits_offset % 8))
|
||||||
|
for j, bb in buf {
|
||||||
|
off := bits_offset / 8 + j
|
||||||
|
if off >= out.len {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out[off] |= bb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// equal returns 1 if v and u are equal, and 0 otherwise.
|
||||||
|
fn (mut v Element) equal(ue Element) int {
|
||||||
|
mut u := ue
|
||||||
|
sa := u.bytes()
|
||||||
|
sv := v.bytes()
|
||||||
|
return subtle.constant_time_compare(sa, sv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
|
||||||
|
fn (mut v Element) swap(mut u Element, cond int) {
|
||||||
|
// mut u := ue
|
||||||
|
m := mask_64_bits(cond)
|
||||||
|
mut t := m & (v.l0 ^ u.l0)
|
||||||
|
v.l0 ^= t
|
||||||
|
u.l0 ^= t
|
||||||
|
t = m & (v.l1 ^ u.l1)
|
||||||
|
v.l1 ^= t
|
||||||
|
u.l1 ^= t
|
||||||
|
t = m & (v.l2 ^ u.l2)
|
||||||
|
v.l2 ^= t
|
||||||
|
u.l2 ^= t
|
||||||
|
t = m & (v.l3 ^ u.l3)
|
||||||
|
v.l3 ^= t
|
||||||
|
u.l3 ^= t
|
||||||
|
t = m & (v.l4 ^ u.l4)
|
||||||
|
v.l4 ^= t
|
||||||
|
u.l4 ^= t
|
||||||
|
}
|
||||||
|
|
||||||
|
// mult_32 sets v = x * y, and returns v.
|
||||||
|
fn (mut v Element) mult_32(x Element, y u32) Element {
|
||||||
|
x0lo, x0hi := mul_51(x.l0, y)
|
||||||
|
x1lo, x1hi := mul_51(x.l1, y)
|
||||||
|
x2lo, x2hi := mul_51(x.l2, y)
|
||||||
|
x3lo, x3hi := mul_51(x.l3, y)
|
||||||
|
x4lo, x4hi := mul_51(x.l4, y)
|
||||||
|
v.l0 = x0lo + 19 * x4hi // carried over per the reduction identity
|
||||||
|
v.l1 = x1lo + x0hi
|
||||||
|
v.l2 = x2lo + x1hi
|
||||||
|
v.l3 = x3lo + x2hi
|
||||||
|
v.l4 = x4lo + x3hi
|
||||||
|
// The hi portions are going to be only 32 bits, plus any previous excess,
|
||||||
|
// so we can skip the carry propagation.
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_endianness(mut buf []byte) []byte {
|
||||||
|
for i := 0; i < buf.len / 2; i++ {
|
||||||
|
buf[i], buf[buf.len - i - 1] = buf[buf.len - i - 1], buf[i]
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
|
@ -0,0 +1,464 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
import os
|
||||||
|
import rand
|
||||||
|
import math.bits
|
||||||
|
import math.big
|
||||||
|
import encoding.hex
|
||||||
|
|
||||||
|
const github_job = os.getenv('GITHUB_JOB')
|
||||||
|
|
||||||
|
fn testsuite_begin() {
|
||||||
|
if edwards25519.github_job != '' {
|
||||||
|
// ensure that the CI does not run flaky tests:
|
||||||
|
rand.seed([u32(0xffff24), 0xabcd])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Element) str() string {
|
||||||
|
return hex.encode(v.bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
const mask_low_52_bits = (u64(1) << 52) - 1
|
||||||
|
|
||||||
|
fn generate_field_element() Element {
|
||||||
|
return Element{
|
||||||
|
l0: rand.u64() & edwards25519.mask_low_52_bits
|
||||||
|
l1: rand.u64() & edwards25519.mask_low_52_bits
|
||||||
|
l2: rand.u64() & edwards25519.mask_low_52_bits
|
||||||
|
l3: rand.u64() & edwards25519.mask_low_52_bits
|
||||||
|
l4: rand.u64() & edwards25519.mask_low_52_bits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// weirdLimbs can be combined to generate a range of edge-case edwards25519 elements.
|
||||||
|
// 0 and -1 are intentionally more weighted, as they combine well.
|
||||||
|
const (
|
||||||
|
two_to_51 = u64(1) << 51
|
||||||
|
two_to_52 = u64(1) << 52
|
||||||
|
weird_limbs_51 = [
|
||||||
|
u64(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
19 - 1,
|
||||||
|
19,
|
||||||
|
0x2aaaaaaaaaaaa,
|
||||||
|
0x5555555555555,
|
||||||
|
two_to_51 - 20,
|
||||||
|
two_to_51 - 19,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
]
|
||||||
|
weird_limbs_52 = [
|
||||||
|
u64(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
19 - 1,
|
||||||
|
19,
|
||||||
|
0x2aaaaaaaaaaaa,
|
||||||
|
0x5555555555555,
|
||||||
|
two_to_51 - 20,
|
||||||
|
two_to_51 - 19,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51 - 1,
|
||||||
|
two_to_51,
|
||||||
|
two_to_51 + 1,
|
||||||
|
two_to_52 - 19,
|
||||||
|
two_to_52 - 1,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
fn generate_weird_field_element() Element {
|
||||||
|
return Element{
|
||||||
|
l0: edwards25519.weird_limbs_52[rand.intn(edwards25519.weird_limbs_52.len)]
|
||||||
|
l1: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)]
|
||||||
|
l2: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)]
|
||||||
|
l3: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)]
|
||||||
|
l4: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (e Element) generate_element() Element {
|
||||||
|
if rand.intn(2) == 0 {
|
||||||
|
return generate_weird_field_element()
|
||||||
|
}
|
||||||
|
return generate_field_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_in_bounds(x Element) bool {
|
||||||
|
return bits.len_64(x.l0) <= 52 && bits.len_64(x.l1) <= 52 && bits.len_64(x.l2) <= 52
|
||||||
|
&& bits.len_64(x.l3) <= 52 && bits.len_64(x.l4) <= 52
|
||||||
|
}
|
||||||
|
|
||||||
|
fn carry_gen(a [5]u64) bool {
|
||||||
|
mut t1 := Element{a[0], a[1], a[2], a[3], a[4]}
|
||||||
|
mut t2 := Element{a[0], a[1], a[2], a[3], a[4]}
|
||||||
|
|
||||||
|
t1.carry_propagate_generic()
|
||||||
|
t2.carry_propagate_generic()
|
||||||
|
|
||||||
|
return t1 == t2 && is_in_bounds(t2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_carry_propagate_generic() {
|
||||||
|
// closures not supported on windows
|
||||||
|
for i := 0; i <= 10; i++ {
|
||||||
|
els := [rand.u64(), rand.u64(), rand.u64(), rand.u64(),
|
||||||
|
rand.u64()]!
|
||||||
|
p := carry_gen(els)
|
||||||
|
assert p == true
|
||||||
|
}
|
||||||
|
res := carry_gen([u64(0xffffffffffffffff), 0xffffffffffffffff, 0xffffffffffffffff,
|
||||||
|
0xffffffffffffffff, 0xffffffffffffffff]!)
|
||||||
|
assert res == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fe_mul_generic() {
|
||||||
|
for i in 0 .. 20 {
|
||||||
|
el := Element{}
|
||||||
|
a := el.generate_element()
|
||||||
|
b := el.generate_element()
|
||||||
|
a1 := a
|
||||||
|
a2 := a
|
||||||
|
|
||||||
|
b1 := b
|
||||||
|
b2 := b
|
||||||
|
|
||||||
|
a1b1 := fe_mul_generic(a1, b1)
|
||||||
|
a2b2 := fe_mul_generic(a2, b2)
|
||||||
|
assert a1b1 == a2b2 && is_in_bounds(a1b1) && is_in_bounds(a2b2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fe_square_generic() {
|
||||||
|
for i in 0 .. 20 {
|
||||||
|
a := generate_field_element()
|
||||||
|
|
||||||
|
a1 := a
|
||||||
|
a2 := a
|
||||||
|
|
||||||
|
a11 := fe_square_generic(a1)
|
||||||
|
a22 := fe_square_generic(a2)
|
||||||
|
assert a11 == a22 && is_in_bounds(a11) && is_in_bounds(a22)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SqrtRatioTest {
|
||||||
|
u string
|
||||||
|
v string
|
||||||
|
was_square int
|
||||||
|
r string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_sqrt_ratio() ? {
|
||||||
|
// From draft-irtf-cfrg-ristretto255-decaf448-00, Appendix A.4.
|
||||||
|
|
||||||
|
tests := [
|
||||||
|
// If u is 0, the function is defined to return (0, TRUE), even if v
|
||||||
|
// is zero. Note that where used in this package, the denominator v
|
||||||
|
// is never zero.
|
||||||
|
SqrtRatioTest{'0000000000000000000000000000000000000000000000000000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', 1, '0000000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
// 0/1 == 0²
|
||||||
|
SqrtRatioTest{'0000000000000000000000000000000000000000000000000000000000000000', '0100000000000000000000000000000000000000000000000000000000000000', 1, '0000000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
// If u is non-zero and v is zero, defined to return (0, FALSE).
|
||||||
|
SqrtRatioTest{'0100000000000000000000000000000000000000000000000000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', 0, '0000000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
// 2/1 is not square in this edwards25519.
|
||||||
|
SqrtRatioTest{'0200000000000000000000000000000000000000000000000000000000000000', '0100000000000000000000000000000000000000000000000000000000000000', 0, '3c5ff1b5d8e4113b871bd052f9e7bcd0582804c266ffb2d4f4203eb07fdb7c54'},
|
||||||
|
// 4/1 == 2²
|
||||||
|
SqrtRatioTest{'0400000000000000000000000000000000000000000000000000000000000000', '0100000000000000000000000000000000000000000000000000000000000000', 1, '0200000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
// 1/4 == (2⁻¹)² == (2^(p-2))² per Euler's theorem
|
||||||
|
SqrtRatioTest{'0100000000000000000000000000000000000000000000000000000000000000', '0400000000000000000000000000000000000000000000000000000000000000', 1, 'f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f'},
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, tt in tests {
|
||||||
|
mut elu := Element{}
|
||||||
|
mut elv := Element{}
|
||||||
|
mut elw := Element{}
|
||||||
|
mut elg := Element{}
|
||||||
|
|
||||||
|
u := elu.set_bytes(hex.decode(tt.u) ?) ?
|
||||||
|
v := elv.set_bytes(hex.decode(tt.v) ?) ?
|
||||||
|
want := elw.set_bytes(hex.decode(tt.r) ?) ?
|
||||||
|
mut got, was_square := elg.sqrt_ratio(u, v)
|
||||||
|
|
||||||
|
assert got.equal(want) != 0
|
||||||
|
assert was_square == tt.was_square
|
||||||
|
// if got.Equal(want) == 0 || wasSquare != tt.wasSquare {
|
||||||
|
// t.Errorf("%d: got (%v, %v), want (%v, %v)", i, got, wasSquare, want, tt.wasSquare)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_set_bytes_normal() ? {
|
||||||
|
for i in 0 .. 15 {
|
||||||
|
mut el := Element{}
|
||||||
|
mut random_inp := rand.bytes(32) ?
|
||||||
|
|
||||||
|
el = el.set_bytes(random_inp.clone()) ?
|
||||||
|
random_inp[random_inp.len - 1] &= (1 << 7) - 1
|
||||||
|
// assert f1(random_inp, el) == true
|
||||||
|
|
||||||
|
assert random_inp == el.bytes()
|
||||||
|
assert is_in_bounds(el) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_set_bytes_reduced() {
|
||||||
|
mut fe := Element{}
|
||||||
|
mut r := Element{}
|
||||||
|
mut random_inp := rand.bytes(32) or { return }
|
||||||
|
|
||||||
|
fe.set_bytes(random_inp) or { return }
|
||||||
|
r.set_bytes(fe.bytes()) or { return }
|
||||||
|
|
||||||
|
assert fe == r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check some fixed vectors from dalek
|
||||||
|
struct FeRTTest {
|
||||||
|
mut:
|
||||||
|
fe Element
|
||||||
|
b []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_set_bytes_from_dalek_test_vectors() ? {
|
||||||
|
mut tests := [
|
||||||
|
FeRTTest{
|
||||||
|
fe: Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676}
|
||||||
|
b: [byte(74), 209, 69, 197, 70, 70, 161, 222, 56, 226, 229, 19, 112, 60, 25, 92, 187,
|
||||||
|
74, 222, 56, 50, 153, 51, 233, 40, 74, 57, 6, 160, 185, 213, 31]
|
||||||
|
},
|
||||||
|
FeRTTest{
|
||||||
|
fe: Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972}
|
||||||
|
b: [byte(199), 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42,
|
||||||
|
32, 83, 250, 44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
for _, mut tt in tests {
|
||||||
|
b := tt.fe.bytes()
|
||||||
|
mut el := Element{}
|
||||||
|
mut fe := el.set_bytes(tt.b) ?
|
||||||
|
|
||||||
|
assert b == tt.b
|
||||||
|
assert fe.equal(tt.fe) == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_equal() {
|
||||||
|
mut x := Element{1, 1, 1, 1, 1}
|
||||||
|
y := Element{5, 4, 3, 2, 1}
|
||||||
|
|
||||||
|
mut eq1 := x.equal(x)
|
||||||
|
assert eq1 == 1
|
||||||
|
|
||||||
|
eq1 = x.equal(y)
|
||||||
|
assert eq1 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_invert() ? {
|
||||||
|
mut x := Element{1, 1, 1, 1, 1}
|
||||||
|
mut one := Element{1, 0, 0, 0, 0}
|
||||||
|
mut xinv := Element{}
|
||||||
|
mut r := Element{}
|
||||||
|
|
||||||
|
xinv.invert(x)
|
||||||
|
r.multiply(x, xinv)
|
||||||
|
r.reduce()
|
||||||
|
|
||||||
|
assert one == r
|
||||||
|
bytes := rand.bytes(32) or { return err }
|
||||||
|
|
||||||
|
x.set_bytes(bytes) ?
|
||||||
|
|
||||||
|
xinv.invert(x)
|
||||||
|
r.multiply(x, xinv)
|
||||||
|
r.reduce()
|
||||||
|
|
||||||
|
assert one == r
|
||||||
|
|
||||||
|
zero := Element{}
|
||||||
|
x.set(zero)
|
||||||
|
|
||||||
|
xx := xinv.invert(x)
|
||||||
|
assert xx == xinv
|
||||||
|
assert xinv.equal(zero) == 1
|
||||||
|
// s := if num % 2 == 0 { 'even' } else { 'odd' }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_mult_32() {
|
||||||
|
for j in 0 .. 10 {
|
||||||
|
mut x := Element{}
|
||||||
|
mut t1 := Element{}
|
||||||
|
y := u32(0)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
t1.mult_32(x, y)
|
||||||
|
}
|
||||||
|
mut ty := Element{}
|
||||||
|
ty.l0 = u64(y)
|
||||||
|
mut t2 := Element{}
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
t2.multiply(x, ty)
|
||||||
|
}
|
||||||
|
assert t1.equal(t2) == 1 && is_in_bounds(t1) && is_in_bounds(t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_selected_and_swap() {
|
||||||
|
a := Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676}
|
||||||
|
b := Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972}
|
||||||
|
|
||||||
|
mut c := Element{}
|
||||||
|
mut d := Element{}
|
||||||
|
|
||||||
|
c.selected(a, b, 1)
|
||||||
|
d.selected(a, b, 0)
|
||||||
|
|
||||||
|
assert c.equal(a) == 1
|
||||||
|
assert d.equal(b) == 1
|
||||||
|
|
||||||
|
c.swap(mut d, 0)
|
||||||
|
assert c.equal(a) == 1
|
||||||
|
assert d.equal(b) == 1
|
||||||
|
|
||||||
|
c.swap(mut d, 1)
|
||||||
|
assert c.equal(b) == 1
|
||||||
|
assert d.equal(a) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests self-consistency between multiply and Square.
|
||||||
|
fn test_consistency_between_mult_and_square() {
|
||||||
|
mut x := Element{1, 1, 1, 1, 1}
|
||||||
|
mut x2 := Element{}
|
||||||
|
mut x2sq := Element{}
|
||||||
|
|
||||||
|
x2.multiply(x, x)
|
||||||
|
x2sq.square(x)
|
||||||
|
|
||||||
|
assert x2 == x2sq
|
||||||
|
|
||||||
|
bytes := rand.bytes(32) or { return }
|
||||||
|
x.set_bytes(bytes) or { return }
|
||||||
|
x2.multiply(x, x)
|
||||||
|
x2sq.square(x)
|
||||||
|
|
||||||
|
assert x2 == x2sq
|
||||||
|
}
|
||||||
|
|
||||||
|
// to_big_integer returns v as a big.Integer.
|
||||||
|
fn (mut v Element) to_big_integer() big.Integer {
|
||||||
|
buf := v.bytes()
|
||||||
|
return big.integer_from_bytes(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// from_big_integer sets v = n, and returns v. The bit length of n must not exceed 256.
|
||||||
|
fn (mut v Element) from_big_integer(n big.Integer) ?Element {
|
||||||
|
if n.binary_str().len > 32 * 8 {
|
||||||
|
return error('invalid edwards25519 element input size')
|
||||||
|
}
|
||||||
|
mut bytes, _ := n.bytes()
|
||||||
|
swap_endianness(mut bytes) // SHOULD I SWAP IT?
|
||||||
|
v.set_bytes(bytes) ?
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Element) from_decimal_string(s string) ?Element {
|
||||||
|
num := big.integer_from_string(s) ?
|
||||||
|
|
||||||
|
v = v.from_big_integer(num) ?
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_bytes_big_equivalence() ? {
|
||||||
|
mut inp := rand.bytes(32) ?
|
||||||
|
el := Element{}
|
||||||
|
mut fe := el.generate_element()
|
||||||
|
mut fe1 := el.generate_element()
|
||||||
|
|
||||||
|
fe.set_bytes(inp) or { panic(err.msg) }
|
||||||
|
inp[inp.len - 1] &= (1 << 7) - 1 // mask the most significant bit
|
||||||
|
|
||||||
|
mut b := big.integer_from_bytes(swap_endianness(mut inp)) // need swap_endianness
|
||||||
|
fe1.from_big_integer(b) or { panic(err.msg) } // do swap_endianness internally
|
||||||
|
|
||||||
|
assert fe == fe1
|
||||||
|
|
||||||
|
mut buf := []byte{len: 32} // pad with zeroes
|
||||||
|
fedtobig := fe1.to_big_integer()
|
||||||
|
mut fedbig_bytes, _ := fedtobig.bytes()
|
||||||
|
copy(buf, fedbig_bytes) // does not need to do swap_endianness
|
||||||
|
|
||||||
|
assert fe.bytes() == buf && is_in_bounds(fe) && is_in_bounds(fe1)
|
||||||
|
// assert big_equivalence(inp, fe, fe1) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_decimal_constants() ? {
|
||||||
|
sqrtm1string := '19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
||||||
|
mut el := Element{}
|
||||||
|
mut exp := el.from_decimal_string(sqrtm1string) ?
|
||||||
|
|
||||||
|
assert sqrt_m1.equal(exp) == 1
|
||||||
|
|
||||||
|
dstring := '37095705934669439343138083508754565189542113879843219016388785533085940283555'
|
||||||
|
exp = el.from_decimal_string(dstring) ?
|
||||||
|
mut d := d_const
|
||||||
|
|
||||||
|
assert d.equal(exp) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_mul_64_to_128() {
|
||||||
|
mut a := u64(5)
|
||||||
|
mut b := u64(5)
|
||||||
|
mut r := mul_64(a, b)
|
||||||
|
|
||||||
|
assert r.lo == 0x19
|
||||||
|
assert r.hi == 0
|
||||||
|
|
||||||
|
a = u64(18014398509481983) // 2^54 - 1
|
||||||
|
b = u64(18014398509481983) // 2^54 - 1
|
||||||
|
r = mul_64(a, b)
|
||||||
|
|
||||||
|
assert r.lo == 0xff80000000000001 && r.hi == 0xfffffffffff
|
||||||
|
|
||||||
|
a = u64(1125899906842661)
|
||||||
|
b = u64(2097155)
|
||||||
|
r = mul_64(a, b)
|
||||||
|
r = add_mul_64(r, a, b)
|
||||||
|
r = add_mul_64(r, a, b)
|
||||||
|
r = add_mul_64(r, a, b)
|
||||||
|
r = add_mul_64(r, a, b)
|
||||||
|
|
||||||
|
assert r.lo == 16888498990613035 && r.hi == 640
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_multiply_distributes_over_add() {
|
||||||
|
for i in 0 .. 10 {
|
||||||
|
el := Element{}
|
||||||
|
x := el.generate_element()
|
||||||
|
y := el.generate_element()
|
||||||
|
z := el.generate_element()
|
||||||
|
mut t1 := Element{}
|
||||||
|
t1.add(x, y)
|
||||||
|
t1.multiply(t1, z)
|
||||||
|
|
||||||
|
// Compute t2 = x*z + y*z
|
||||||
|
mut t2 := Element{}
|
||||||
|
mut t3 := Element{}
|
||||||
|
t2.multiply(x, z)
|
||||||
|
t3.multiply(y, z)
|
||||||
|
t2.add(t2, t3)
|
||||||
|
assert t1.equal(t2) == 1 && is_in_bounds(t1) && is_in_bounds(t2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
// extended_coordinates returns v in extended coordinates (X:Y:Z:T) where
|
||||||
|
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||||
|
fn (mut v Point) extended_coordinates() (Element, Element, Element, Element) {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap. Don't change the style without making
|
||||||
|
// sure it doesn't increase the inliner cost.
|
||||||
|
mut e := []Element{len: 4}
|
||||||
|
x, y, z, t := v.extended_coordinates_generic(mut e)
|
||||||
|
return x, y, z, t
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Point) extended_coordinates_generic(mut e []Element) (Element, Element, Element, Element) {
|
||||||
|
check_initialized(v)
|
||||||
|
x := e[0].set(v.x)
|
||||||
|
y := e[1].set(v.y)
|
||||||
|
z := e[2].set(v.z)
|
||||||
|
t := e[3].set(v.t)
|
||||||
|
return x, y, z, t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given k > 0, set s = s**(2*i).
|
||||||
|
fn (mut s Scalar) pow2k(k int) {
|
||||||
|
for i := 0; i < k; i++ {
|
||||||
|
s.multiply(s, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set_extended_coordinates sets v = (X:Y:Z:T) in extended coordinates where
|
||||||
|
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||||
|
//
|
||||||
|
// If the coordinates are invalid or don't represent a valid point on the curve,
|
||||||
|
// set_extended_coordinates returns nil and an error and the receiver is
|
||||||
|
// unchanged. Otherwise, set_extended_coordinates returns v.
|
||||||
|
fn (mut v Point) set_extended_coordinates(x Element, y Element, z Element, t Element) ?Point {
|
||||||
|
if !is_on_curve(x, y, z, t) {
|
||||||
|
return error('edwards25519: invalid point coordinates')
|
||||||
|
}
|
||||||
|
v.x.set(x)
|
||||||
|
v.y.set(y)
|
||||||
|
v.z.set(z)
|
||||||
|
v.t.set(t)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_on_curve(x Element, y Element, z Element, t Element) bool {
|
||||||
|
mut lhs := Element{}
|
||||||
|
mut rhs := Element{}
|
||||||
|
|
||||||
|
mut xx := Element{}
|
||||||
|
xx.square(x)
|
||||||
|
|
||||||
|
mut yy := Element{}
|
||||||
|
yy.square(y)
|
||||||
|
|
||||||
|
mut zz := Element{}
|
||||||
|
zz.square(z)
|
||||||
|
// zz := new(Element).Square(Z)
|
||||||
|
|
||||||
|
mut tt := Element{}
|
||||||
|
tt.square(t)
|
||||||
|
// tt := new(Element).Square(T)
|
||||||
|
// -x² + y² = 1 + dx²y²
|
||||||
|
// -(X/Z)² + (Y/Z)² = 1 + d(T/Z)²
|
||||||
|
// -X² + Y² = Z² + dT²
|
||||||
|
lhs.subtract(yy, xx)
|
||||||
|
mut d := d_const
|
||||||
|
rhs.multiply(d, tt)
|
||||||
|
rhs.add(rhs, zz)
|
||||||
|
if lhs.equal(rhs) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// xy = T/Z
|
||||||
|
// XY/Z² = T/Z
|
||||||
|
// XY = TZ
|
||||||
|
lhs.multiply(x, y)
|
||||||
|
rhs.multiply(t, z)
|
||||||
|
return lhs.equal(rhs) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// `bytes_montgomery` converts v to a point on the birationally-equivalent
|
||||||
|
// Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding
|
||||||
|
// according to RFC 7748.
|
||||||
|
//
|
||||||
|
// Note that bytes_montgomery only encodes the u-coordinate, so v and -v encode
|
||||||
|
// to the same value. If v is the identity point, bytes_montgomery returns 32
|
||||||
|
// zero bytes, analogously to the X25519 function.
|
||||||
|
pub fn (mut v Point) bytes_montgomery() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
mut buf := [32]byte{}
|
||||||
|
return v.bytes_montgomery_generic(mut buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Point) bytes_montgomery_generic(mut buf [32]byte) []byte {
|
||||||
|
check_initialized(v)
|
||||||
|
|
||||||
|
// RFC 7748, Section 4.1 provides the bilinear map to calculate the
|
||||||
|
// Montgomery u-coordinate
|
||||||
|
//
|
||||||
|
// u = (1 + y) / (1 - y)
|
||||||
|
//
|
||||||
|
// where y = Y / Z.
|
||||||
|
|
||||||
|
mut y := Element{}
|
||||||
|
mut recip := Element{}
|
||||||
|
mut u := Element{}
|
||||||
|
|
||||||
|
y.multiply(v.y, y.invert(v.z)) // y = Y / Z
|
||||||
|
recip.invert(recip.subtract(fe_one, &y)) // r = 1/(1 - y)
|
||||||
|
u.multiply(u.add(fe_one, y), recip) // u = (1 + y)*r
|
||||||
|
|
||||||
|
return copy_field_element(mut buf, mut u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `mult_by_cofactor` sets v = 8 * p, and returns v.
|
||||||
|
pub fn (mut v Point) mult_by_cofactor(p Point) Point {
|
||||||
|
check_initialized(p)
|
||||||
|
mut result := ProjectiveP1{}
|
||||||
|
mut pp := ProjectiveP2{}
|
||||||
|
pp.from_p3(p)
|
||||||
|
result.double(pp)
|
||||||
|
pp.from_p1(result)
|
||||||
|
result.double(pp)
|
||||||
|
pp.from_p1(result)
|
||||||
|
result.double(pp)
|
||||||
|
return v.from_p1(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `invert` sets s to the inverse of a nonzero scalar v, and returns s.
|
||||||
|
//
|
||||||
|
// If t is zero, invert returns zero.
|
||||||
|
pub fn (mut s Scalar) invert(t Scalar) Scalar {
|
||||||
|
// Uses a hardcoded sliding window of width 4.
|
||||||
|
mut table := [8]Scalar{}
|
||||||
|
mut tt := Scalar{}
|
||||||
|
tt.multiply(t, t)
|
||||||
|
table[0] = t
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
table[i + 1].multiply(table[i], tt)
|
||||||
|
}
|
||||||
|
// Now table = [t**1, t**3, t**7, t**11, t**13, t**15]
|
||||||
|
// so t**k = t[k/2] for odd k
|
||||||
|
|
||||||
|
// To compute the sliding window digits, use the following Sage script:
|
||||||
|
|
||||||
|
// sage: import itertools
|
||||||
|
// sage: def sliding_window(w,k):
|
||||||
|
// ....: digits = []
|
||||||
|
// ....: while k > 0:
|
||||||
|
// ....: if k % 2 == 1:
|
||||||
|
// ....: kmod = k % (2**w)
|
||||||
|
// ....: digits.append(kmod)
|
||||||
|
// ....: k = k - kmod
|
||||||
|
// ....: else:
|
||||||
|
// ....: digits.append(0)
|
||||||
|
// ....: k = k // 2
|
||||||
|
// ....: return digits
|
||||||
|
|
||||||
|
// Now we can compute s roughly as follows:
|
||||||
|
|
||||||
|
// sage: s = 1
|
||||||
|
// sage: for coeff in reversed(sliding_window(4,l-2)):
|
||||||
|
// ....: s = s*s
|
||||||
|
// ....: if coeff > 0 :
|
||||||
|
// ....: s = s*t**coeff
|
||||||
|
|
||||||
|
// This works on one bit at a time, with many runs of zeros.
|
||||||
|
// The digits can be collapsed into [(count, coeff)] as follows:
|
||||||
|
|
||||||
|
// sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))]
|
||||||
|
|
||||||
|
// Entries of the form (k, 0) turn into pow2k(k)
|
||||||
|
// Entries of the form (1, coeff) turn into a squaring and then a table lookup.
|
||||||
|
// We can fold the squaring into the previous pow2k(k) as pow2k(k+1).
|
||||||
|
|
||||||
|
s = table[1 / 2]
|
||||||
|
s.pow2k(127 + 1)
|
||||||
|
s.multiply(s, table[1 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[9 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[11 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[13 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[15 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[7 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[15 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[5 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[1 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[15 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[15 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[7 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[3 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[11 / 2])
|
||||||
|
s.pow2k(5 + 1)
|
||||||
|
s.multiply(s, table[11 / 2])
|
||||||
|
s.pow2k(9 + 1)
|
||||||
|
s.multiply(s, table[9 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[3 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[3 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[3 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[9 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[7 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[3 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[13 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[7 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[9 / 2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.multiply(s, table[15 / 2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.multiply(s, table[11 / 2])
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// `multi_scalar_mult` sets v = sum(scalars[i] * points[i]), and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends only on the lengths of the two slices, which must match.
|
||||||
|
pub fn (mut v Point) multi_scalar_mult(scalars []Scalar, points []Point) Point {
|
||||||
|
if scalars.len != points.len {
|
||||||
|
panic('edwards25519: called multi_scalar_mult with different size inputs')
|
||||||
|
}
|
||||||
|
check_initialized(...points)
|
||||||
|
|
||||||
|
mut sc := scalars.clone()
|
||||||
|
// Proceed as in the single-base case, but share doublings
|
||||||
|
// between each point in the multiscalar equation.
|
||||||
|
|
||||||
|
// Build lookup tables for each point
|
||||||
|
mut tables := []ProjLookupTable{len: points.len}
|
||||||
|
for i, _ in tables {
|
||||||
|
tables[i].from_p3(points[i])
|
||||||
|
}
|
||||||
|
// Compute signed radix-16 digits for each scalar
|
||||||
|
// digits := make([][64]int8, len(scalars))
|
||||||
|
mut digits := [][]i8{len: sc.len, init: []i8{len: 64, cap: 64}}
|
||||||
|
|
||||||
|
for j, _ in digits {
|
||||||
|
digits[j] = sc[j].signed_radix16()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap first loop iteration to save computing 16*identity
|
||||||
|
mut multiple := ProjectiveCached{}
|
||||||
|
mut tmp1 := ProjectiveP1{}
|
||||||
|
mut tmp2 := ProjectiveP2{}
|
||||||
|
// Lookup-and-add the appropriate multiple of each input point
|
||||||
|
for k, _ in tables {
|
||||||
|
tables[k].select_into(mut multiple, digits[k][63])
|
||||||
|
tmp1.add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords
|
||||||
|
v.from_p1(tmp1) // update v
|
||||||
|
}
|
||||||
|
tmp2.from_p3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||||
|
for r := 62; r >= 0; r-- {
|
||||||
|
tmp1.double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||||
|
v.from_p1(tmp1) // v = 16*(prev) in P3 coords
|
||||||
|
// Lookup-and-add the appropriate multiple of each input point
|
||||||
|
for s, _ in tables {
|
||||||
|
tables[s].select_into(mut multiple, digits[s][r])
|
||||||
|
tmp1.add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords
|
||||||
|
v.from_p1(tmp1) // update v
|
||||||
|
}
|
||||||
|
tmp2.from_p3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// `vartime_multiscalar_mult` sets v = sum(scalars[i] * points[i]), and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends on the inputs.
|
||||||
|
pub fn (mut v Point) vartime_multiscalar_mult(scalars []Scalar, points []Point) Point {
|
||||||
|
if scalars.len != points.len {
|
||||||
|
panic('edwards25519: called VarTimeMultiScalarMult with different size inputs')
|
||||||
|
}
|
||||||
|
check_initialized(...points)
|
||||||
|
|
||||||
|
// Generalize double-base NAF computation to arbitrary sizes.
|
||||||
|
// Here all the points are dynamic, so we only use the smaller
|
||||||
|
// tables.
|
||||||
|
|
||||||
|
// Build lookup tables for each point
|
||||||
|
mut tables := []NafLookupTable5{len: points.len}
|
||||||
|
for i, _ in tables {
|
||||||
|
tables[i].from_p3(points[i])
|
||||||
|
}
|
||||||
|
mut sc := scalars.clone()
|
||||||
|
// Compute a NAF for each scalar
|
||||||
|
// mut nafs := make([][256]int8, len(scalars))
|
||||||
|
mut nafs := [][]i8{len: sc.len, init: []i8{len: 256, cap: 256}}
|
||||||
|
for j, _ in nafs {
|
||||||
|
nafs[j] = sc[j].non_adjacent_form(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
mut multiple := ProjectiveCached{}
|
||||||
|
mut tmp1 := ProjectiveP1{}
|
||||||
|
mut tmp2 := ProjectiveP2{}
|
||||||
|
tmp2.zero()
|
||||||
|
|
||||||
|
// Move from high to low bits, doubling the accumulator
|
||||||
|
// at each iteration and checking whether there is a nonzero
|
||||||
|
// coefficient to look up a multiple of.
|
||||||
|
//
|
||||||
|
// Skip trying to find the first nonzero coefficent, because
|
||||||
|
// searching might be more work than a few extra doublings.
|
||||||
|
// k == i, l == j
|
||||||
|
for k := 255; k >= 0; k-- {
|
||||||
|
tmp1.double(tmp2)
|
||||||
|
|
||||||
|
for l, _ in nafs {
|
||||||
|
if nafs[l][k] > 0 {
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
tables[l].select_into(mut multiple, nafs[l][k])
|
||||||
|
tmp1.add(v, multiple)
|
||||||
|
} else if nafs[l][k] < 0 {
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
tables[l].select_into(mut multiple, -nafs[l][k])
|
||||||
|
tmp1.sub(v, multiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp2.from_p1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.from_p2(tmp2)
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
import os
|
||||||
|
import rand
|
||||||
|
import encoding.hex
|
||||||
|
|
||||||
|
const github_job = os.getenv('GITHUB_JOB')
|
||||||
|
|
||||||
|
fn testsuite_begin() {
|
||||||
|
if edwards25519.github_job != '' {
|
||||||
|
// ensure that the CI does not run flaky tests:
|
||||||
|
rand.seed([u32(0xffff24), 0xabcd])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test_bytes_montgomery tests the set_bytes_with_clamping+bytes_montgomery path
|
||||||
|
// equivalence to curve25519.X25519 for basepoint scalar multiplications.
|
||||||
|
//
|
||||||
|
// Note that you can't actually implement X25519 with this package because
|
||||||
|
// there is no SetBytesMontgomery, and it would not be possible to implement
|
||||||
|
// it properly: points on the twist would get rejected, and the Scalar returned
|
||||||
|
// by set_bytes_with_clamping does not preserve its cofactor-clearing properties.
|
||||||
|
//
|
||||||
|
// Disabled curve25519 not available yet, but maybe can use own curve25519
|
||||||
|
/*
|
||||||
|
fn fn_mon(scalar [32]byte) bool {
|
||||||
|
mut s := new_scalar().set_bytes_with_clamping(scalar[..])
|
||||||
|
p := (&Point{}).scalar_base_mult(s)
|
||||||
|
got := p.bytes_montgomery()
|
||||||
|
want, _ := curve25519.X25519(scalar[..], curve25519.Basepoint)
|
||||||
|
return bytes.equal(got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_bytes_montgomery() {
|
||||||
|
/* f := fn(scalar [32]byte) bool {
|
||||||
|
s := new_scalar().set_bytes_with_clamping(scalar[..])
|
||||||
|
p := (&Point{}).scalar_base_mult(s)
|
||||||
|
got := p.bytes_montgomery()
|
||||||
|
want, _ := curve25519.X25519(scalar[..], curve25519.Basepoint)
|
||||||
|
return bytes.equal(got, want)
|
||||||
|
} */
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
fn test_bytes_montgomery_sodium() ? {
|
||||||
|
// Generated with libsodium.js 1.0.18
|
||||||
|
// crypto_sign_keypair().pubkey
|
||||||
|
pubkey := '3bf918ffc2c955dc895bf145f566fb96623c1cadbe040091175764b5fde322c0'
|
||||||
|
mut p := Point{}
|
||||||
|
p.set_bytes(hex.decode(pubkey) ?) ?
|
||||||
|
|
||||||
|
// crypto_sign_ed25519_pk_to_curve25519(pubkey)
|
||||||
|
want := 'efc6c9d0738e9ea18d738ad4a2653631558931b0f1fde4dd58c436d19686dc28'
|
||||||
|
got := hex.encode(p.bytes_montgomery())
|
||||||
|
assert got == want
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_bytes_montgomery_infinity() {
|
||||||
|
mut p := new_identity_point()
|
||||||
|
want := '0000000000000000000000000000000000000000000000000000000000000000'
|
||||||
|
got := hex.encode(p.bytes_montgomery())
|
||||||
|
|
||||||
|
assert got == want
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
loworder_string = '26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85'
|
||||||
|
loworder_bytes = hex.decode(loworder_string) or { panic(err.msg) }
|
||||||
|
)
|
||||||
|
|
||||||
|
fn fn_cofactor(mut data []byte) bool {
|
||||||
|
if data.len != 64 {
|
||||||
|
panic('err.msg')
|
||||||
|
}
|
||||||
|
mut loworder := Point{}
|
||||||
|
loworder.set_bytes(edwards25519.loworder_bytes) or { panic(err.msg) }
|
||||||
|
|
||||||
|
mut s := new_scalar()
|
||||||
|
mut p := Point{}
|
||||||
|
mut p8 := Point{}
|
||||||
|
s.set_uniform_bytes(data) or { panic(err.msg) }
|
||||||
|
p.scalar_base_mult(mut s)
|
||||||
|
p8.mult_by_cofactor(p)
|
||||||
|
|
||||||
|
assert check_on_curve(p8) == true
|
||||||
|
|
||||||
|
// 8 * p == (8 * s) * B
|
||||||
|
mut sc := Scalar{
|
||||||
|
s: [32]byte{}
|
||||||
|
}
|
||||||
|
sc.s[0] = byte(0x08)
|
||||||
|
s.multiply(s, sc)
|
||||||
|
mut pp := Point{}
|
||||||
|
pp.scalar_base_mult(mut s)
|
||||||
|
if p8.equal(pp) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8 * p == 8 * (loworder + p)
|
||||||
|
pp.add(p, loworder)
|
||||||
|
pp.mult_by_cofactor(pp)
|
||||||
|
if p8.equal(pp) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8 * p == p + p + p + p + p + p + p + p
|
||||||
|
pp.set(new_identity_point())
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
pp.add(pp, p)
|
||||||
|
}
|
||||||
|
return p8.equal(pp) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_mult_by_cofactor() ? {
|
||||||
|
mut loworder := Point{}
|
||||||
|
mut data := rand.bytes(64) ?
|
||||||
|
|
||||||
|
assert fn_cofactor(mut data) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invert_works(mut xinv Scalar, x NotZeroScalar) bool {
|
||||||
|
xinv.invert(x)
|
||||||
|
mut check := Scalar{}
|
||||||
|
check.multiply(x, xinv)
|
||||||
|
return check == sc_one && is_reduced(xinv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_invert() {
|
||||||
|
nsc := generate_notzero_scalar(5) or { panic(err.msg) }
|
||||||
|
mut xsc := generate_scalar(5) or { panic(err.msg) }
|
||||||
|
assert invert_works(mut xsc, nsc) == true
|
||||||
|
|
||||||
|
mut zero := new_scalar()
|
||||||
|
mut xx := new_scalar()
|
||||||
|
xx.invert(zero)
|
||||||
|
assert xx.equal(zero) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_multiscalarmultmatchesbasemult() {
|
||||||
|
for i in 0 .. 6 {
|
||||||
|
x := generate_scalar(100) or { panic(err.msg) }
|
||||||
|
y := generate_scalar(5) or { panic(err.msg) }
|
||||||
|
z := generate_scalar(2) or { panic(err.msg) }
|
||||||
|
assert multiscalarmultmatchesbasemult(x, y, z) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multiscalarmultmatchesbasemult(xx Scalar, yy Scalar, zz Scalar) bool {
|
||||||
|
mut x := xx
|
||||||
|
mut y := yy
|
||||||
|
mut z := zz
|
||||||
|
|
||||||
|
mut p := Point{}
|
||||||
|
mut q1 := Point{}
|
||||||
|
mut q2 := Point{}
|
||||||
|
mut q3 := Point{}
|
||||||
|
mut check := Point{}
|
||||||
|
mut b := new_generator_point()
|
||||||
|
|
||||||
|
p.multi_scalar_mult([x, y, z], [b, b, b])
|
||||||
|
|
||||||
|
q1.scalar_base_mult(mut x)
|
||||||
|
q2.scalar_base_mult(mut y)
|
||||||
|
q3.scalar_base_mult(mut z)
|
||||||
|
check.add(q1, q2)
|
||||||
|
check.add(check, q3)
|
||||||
|
|
||||||
|
check_on_curve(p, check, q1, q2, q3)
|
||||||
|
return p.equal(check) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vartime_multiscala_rmultmatches_basemult(xx Scalar, yy Scalar, zz Scalar) bool {
|
||||||
|
mut x := xx
|
||||||
|
mut y := yy
|
||||||
|
mut z := zz
|
||||||
|
mut p := Point{}
|
||||||
|
mut q1 := Point{}
|
||||||
|
mut q2 := Point{}
|
||||||
|
mut q3 := Point{}
|
||||||
|
mut check := Point{}
|
||||||
|
mut b := new_generator_point()
|
||||||
|
|
||||||
|
p.vartime_multiscalar_mult([x, y, z], [b, b, b])
|
||||||
|
|
||||||
|
q1.scalar_base_mult(mut x)
|
||||||
|
q2.scalar_base_mult(mut y)
|
||||||
|
q3.scalar_base_mult(mut z)
|
||||||
|
check.add(q1, q2)
|
||||||
|
check.add(check, q3)
|
||||||
|
|
||||||
|
check_on_curve(p, check, q1, q2, q3)
|
||||||
|
return p.equal(check) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_vartimemultiscalarmultmatchesbasemult() {
|
||||||
|
for i in 0 .. 5 {
|
||||||
|
x := generate_scalar(100) or { panic(err.msg) }
|
||||||
|
y := generate_scalar(5) or { panic(err.msg) }
|
||||||
|
z := generate_scalar(2) or { panic(err.msg) }
|
||||||
|
assert vartime_multiscala_rmultmatches_basemult(x, y, z) == true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,551 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
const (
|
||||||
|
// d is a constant in the curve equation.
|
||||||
|
d_bytes = [byte(0xa3), 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75, 0xab, 0xd8, 0x41, 0x41,
|
||||||
|
0x4d, 0x0a, 0x70, 0x00, 0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c, 0x73, 0xfe, 0x6f,
|
||||||
|
0x2b, 0xee, 0x6c, 0x03, 0x52]
|
||||||
|
id_bytes = [byte(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]
|
||||||
|
gen_bytes = [byte(0x58), 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66]
|
||||||
|
d_const = d_const_generate() or { panic(err.msg) }
|
||||||
|
d2_const = d2_const_generate() or { panic(err.msg) }
|
||||||
|
// id_point is the point at infinity.
|
||||||
|
id_point = id_point_generate() or { panic(err.msg) }
|
||||||
|
// generator point
|
||||||
|
gen_point = generator() or { panic(err.msg) }
|
||||||
|
)
|
||||||
|
|
||||||
|
fn d_const_generate() ?Element {
|
||||||
|
mut v := Element{}
|
||||||
|
v.set_bytes(edwards25519.d_bytes) ?
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn d2_const_generate() ?Element {
|
||||||
|
mut v := Element{}
|
||||||
|
v.add(edwards25519.d_const, edwards25519.d_const)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// id_point_generate is the point at infinity.
|
||||||
|
fn id_point_generate() ?Point {
|
||||||
|
mut p := Point{}
|
||||||
|
p.set_bytes(edwards25519.id_bytes) ?
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// generator is the canonical curve basepoint. See TestGenerator for the
|
||||||
|
// correspondence of this encoding with the values in RFC 8032.
|
||||||
|
fn generator() ?Point {
|
||||||
|
mut p := Point{}
|
||||||
|
p.set_bytes(edwards25519.gen_bytes) ?
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point types.
|
||||||
|
|
||||||
|
struct ProjectiveP1 {
|
||||||
|
mut:
|
||||||
|
x Element
|
||||||
|
y Element
|
||||||
|
z Element
|
||||||
|
t Element
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProjectiveP2 {
|
||||||
|
mut:
|
||||||
|
x Element
|
||||||
|
y Element
|
||||||
|
z Element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point represents a point on the edwards25519 curve.
|
||||||
|
//
|
||||||
|
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||||
|
// are allowed to alias.
|
||||||
|
//
|
||||||
|
// The zero value is NOT valid, and it may be used only as a receiver.
|
||||||
|
pub struct Point {
|
||||||
|
mut:
|
||||||
|
// The point is internally represented in extended coordinates (x, y, z, T)
|
||||||
|
// where x = x/z, y = y/z, and xy = T/z per https://eprint.iacr.org/2008/522.
|
||||||
|
x Element
|
||||||
|
y Element
|
||||||
|
z Element
|
||||||
|
t Element
|
||||||
|
// Make the type not comparable (i.e. used with == or as a map key), as
|
||||||
|
// equivalent points can be represented by different Go values.
|
||||||
|
// _ incomparable
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_initialized(points ...Point) {
|
||||||
|
for _, p in points {
|
||||||
|
if p.x == fe_zero && p.y == fe_zero {
|
||||||
|
panic('edwards25519: use of uninitialized Point')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProjectiveCached {
|
||||||
|
mut:
|
||||||
|
ypx Element // y + x
|
||||||
|
ymx Element // y - x
|
||||||
|
z Element
|
||||||
|
t2d Element
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AffineCached {
|
||||||
|
mut:
|
||||||
|
ypx Element // y + x
|
||||||
|
ymx Element // y - x
|
||||||
|
t2d Element
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP2) zero() ProjectiveP2 {
|
||||||
|
v.x.zero()
|
||||||
|
v.y.one()
|
||||||
|
v.z.one()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// set_bytes sets v = x, where x is a 32-byte encoding of v. If x does not
|
||||||
|
// represent a valid point on the curve, set_bytes returns nil and an error and
|
||||||
|
// the receiver is unchanged. Otherwise, set_bytes returns v.
|
||||||
|
//
|
||||||
|
// Note that set_bytes accepts all non-canonical encodings of valid points.
|
||||||
|
// That is, it follows decoding rules that match most implementations in
|
||||||
|
// the ecosystem rather than RFC 8032.
|
||||||
|
pub fn (mut v Point) set_bytes(x []byte) ?Point {
|
||||||
|
// Specifically, the non-canonical encodings that are accepted are
|
||||||
|
// 1) the ones where the edwards25519 element is not reduced (see the
|
||||||
|
// (*edwards25519.Element).set_bytes docs) and
|
||||||
|
// 2) the ones where the x-coordinate is zero and the sign bit is set.
|
||||||
|
//
|
||||||
|
// This is consistent with crypto/ed25519/internal/edwards25519. Read more
|
||||||
|
// at https://hdevalence.ca/blog/2020-10-04-its-25519am, specifically the
|
||||||
|
// "Canonical A, R" section.
|
||||||
|
mut el0 := Element{}
|
||||||
|
y := el0.set_bytes(x) or { return error('edwards25519: invalid point encoding length') }
|
||||||
|
|
||||||
|
// -x² + y² = 1 + dx²y²
|
||||||
|
// x² + dx²y² = x²(dy² + 1) = y² - 1
|
||||||
|
// x² = (y² - 1) / (dy² + 1)
|
||||||
|
|
||||||
|
// u = y² - 1
|
||||||
|
mut el1 := Element{}
|
||||||
|
y2 := el1.square(y)
|
||||||
|
mut el2 := Element{}
|
||||||
|
u := el2.subtract(y2, fe_one)
|
||||||
|
|
||||||
|
// v = dy² + 1
|
||||||
|
mut el3 := Element{}
|
||||||
|
mut vv := el3.multiply(y2, edwards25519.d_const)
|
||||||
|
vv = vv.add(vv, fe_one)
|
||||||
|
|
||||||
|
// x = +√(u/v)
|
||||||
|
mut el4 := Element{}
|
||||||
|
mut xx, was_square := el4.sqrt_ratio(u, vv)
|
||||||
|
if was_square == 0 {
|
||||||
|
return error('edwards25519: invalid point encoding')
|
||||||
|
}
|
||||||
|
|
||||||
|
// selected the negative square root if the sign bit is set.
|
||||||
|
mut el5 := Element{}
|
||||||
|
xx_neg := el5.negate(xx)
|
||||||
|
xx.selected(xx_neg, xx, int(x[31] >> 7))
|
||||||
|
|
||||||
|
v.x.set(xx)
|
||||||
|
v.y.set(y)
|
||||||
|
v.z.one()
|
||||||
|
v.t.multiply(xx, y) // xy = T / z
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// `set` sets v = u, and returns v.
|
||||||
|
pub fn (mut v Point) set(u Point) Point {
|
||||||
|
v = u
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// `new_identity_point` returns a new Point set to the identity.
|
||||||
|
pub fn new_identity_point() Point {
|
||||||
|
mut p := Point{}
|
||||||
|
return p.set(edwards25519.id_point)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `new_generator_point` returns a new Point set to the canonical generator.
|
||||||
|
pub fn new_generator_point() Point {
|
||||||
|
mut p := Point{}
|
||||||
|
return p.set(edwards25519.gen_point)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveCached) zero() ProjectiveCached {
|
||||||
|
v.ypx.one()
|
||||||
|
v.ymx.one()
|
||||||
|
v.z.one()
|
||||||
|
v.t2d.zero()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v AffineCached) zero() AffineCached {
|
||||||
|
v.ypx.one()
|
||||||
|
v.ymx.one()
|
||||||
|
v.t2d.zero()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding.
|
||||||
|
|
||||||
|
// `bytes` returns the canonical 32-byte encoding of v, according to RFC 8032,
|
||||||
|
// Section 5.1.2.
|
||||||
|
pub fn (mut v Point) bytes() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
mut buf := [32]byte{}
|
||||||
|
return v.bytes_generic(mut buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Point) bytes_generic(mut buf [32]byte) []byte {
|
||||||
|
check_initialized(v)
|
||||||
|
|
||||||
|
mut zinv := Element{}
|
||||||
|
mut x := Element{}
|
||||||
|
mut y := Element{}
|
||||||
|
zinv.invert(v.z) // zinv = 1 / z
|
||||||
|
x.multiply(v.x, zinv) // x = x / z
|
||||||
|
y.multiply(v.y, zinv) // y = y / z
|
||||||
|
|
||||||
|
mut out := copy_field_element(mut buf, mut y)
|
||||||
|
unsafe {
|
||||||
|
// out[31] |= byte(x.is_negative() << 7) //original one
|
||||||
|
out[31] |= byte(x.is_negative() * 128) // x << 7 == x * 2^7
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_field_element(mut buf [32]byte, mut v Element) []byte {
|
||||||
|
// this fail in test
|
||||||
|
/*
|
||||||
|
copy(buf[..], v.bytes())
|
||||||
|
return buf[..]
|
||||||
|
*/
|
||||||
|
|
||||||
|
// this pass the test
|
||||||
|
mut out := []byte{len: 32}
|
||||||
|
for i := 0; i <= buf.len - 1; i++ {
|
||||||
|
out[i] = v.bytes()[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversions.
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP2) from_p1(p ProjectiveP1) ProjectiveP2 {
|
||||||
|
v.x.multiply(p.x, p.t)
|
||||||
|
v.y.multiply(p.y, p.z)
|
||||||
|
v.z.multiply(p.z, p.t)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP2) from_p3(p Point) ProjectiveP2 {
|
||||||
|
v.x.set(p.x)
|
||||||
|
v.y.set(p.y)
|
||||||
|
v.z.set(p.z)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Point) from_p1(p ProjectiveP1) Point {
|
||||||
|
v.x.multiply(p.x, p.t)
|
||||||
|
v.y.multiply(p.y, p.z)
|
||||||
|
v.z.multiply(p.z, p.t)
|
||||||
|
v.t.multiply(p.x, p.y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Point) from_p2(p ProjectiveP2) Point {
|
||||||
|
v.x.multiply(p.x, p.z)
|
||||||
|
v.y.multiply(p.y, p.z)
|
||||||
|
v.z.square(p.z)
|
||||||
|
v.t.multiply(p.x, p.y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveCached) from_p3(p Point) ProjectiveCached {
|
||||||
|
v.ypx.add(p.y, p.x)
|
||||||
|
v.ymx.subtract(p.y, p.x)
|
||||||
|
v.z.set(p.z)
|
||||||
|
v.t2d.multiply(p.t, edwards25519.d2_const)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v AffineCached) from_p3(p Point) AffineCached {
|
||||||
|
v.ypx.add(p.y, p.x)
|
||||||
|
v.ymx.subtract(p.y, p.x)
|
||||||
|
v.t2d.multiply(p.t, edwards25519.d2_const)
|
||||||
|
|
||||||
|
mut invz := Element{}
|
||||||
|
invz.invert(p.z)
|
||||||
|
v.ypx.multiply(v.ypx, invz)
|
||||||
|
v.ymx.multiply(v.ymx, invz)
|
||||||
|
v.t2d.multiply(v.t2d, invz)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Re)addition and subtraction.
|
||||||
|
|
||||||
|
// `add` sets v = p + q, and returns v.
|
||||||
|
pub fn (mut v Point) add(p Point, q Point) Point {
|
||||||
|
check_initialized(p, q)
|
||||||
|
mut pc := ProjectiveCached{}
|
||||||
|
mut p1 := ProjectiveP1{}
|
||||||
|
qcached := pc.from_p3(q)
|
||||||
|
|
||||||
|
result := p1.add(p, qcached)
|
||||||
|
return v.from_p1(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `subtract` sets v = p - q, and returns v.
|
||||||
|
pub fn (mut v Point) subtract(p Point, q Point) Point {
|
||||||
|
check_initialized(p, q)
|
||||||
|
mut pc := ProjectiveCached{}
|
||||||
|
mut p1 := ProjectiveP1{}
|
||||||
|
qcached := pc.from_p3(q)
|
||||||
|
result := p1.sub(p, qcached)
|
||||||
|
return v.from_p1(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP1) add(p Point, q ProjectiveCached) ProjectiveP1 {
|
||||||
|
// var ypx, ymx, pp, mm, tt2d, zz2 edwards25519.Element
|
||||||
|
mut ypx := Element{}
|
||||||
|
mut ymx := Element{}
|
||||||
|
mut pp := Element{}
|
||||||
|
mut mm := Element{}
|
||||||
|
mut tt2d := Element{}
|
||||||
|
mut zz2 := Element{}
|
||||||
|
|
||||||
|
ypx.add(p.y, p.x)
|
||||||
|
ymx.subtract(p.y, p.x)
|
||||||
|
|
||||||
|
pp.multiply(ypx, q.ypx)
|
||||||
|
mm.multiply(ymx, q.ymx)
|
||||||
|
tt2d.multiply(p.t, q.t2d)
|
||||||
|
zz2.multiply(p.z, q.z)
|
||||||
|
|
||||||
|
zz2.add(zz2, zz2)
|
||||||
|
|
||||||
|
v.x.subtract(pp, mm)
|
||||||
|
v.y.add(pp, mm)
|
||||||
|
v.z.add(zz2, tt2d)
|
||||||
|
v.t.subtract(zz2, tt2d)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP1) sub(p Point, q ProjectiveCached) ProjectiveP1 {
|
||||||
|
mut ypx := Element{}
|
||||||
|
mut ymx := Element{}
|
||||||
|
mut pp := Element{}
|
||||||
|
mut mm := Element{}
|
||||||
|
mut tt2d := Element{}
|
||||||
|
mut zz2 := Element{}
|
||||||
|
|
||||||
|
ypx.add(p.y, p.x)
|
||||||
|
ymx.subtract(p.y, p.x)
|
||||||
|
|
||||||
|
pp.multiply(&ypx, q.ymx) // flipped sign
|
||||||
|
mm.multiply(&ymx, q.ypx) // flipped sign
|
||||||
|
tt2d.multiply(p.t, q.t2d)
|
||||||
|
zz2.multiply(p.z, q.z)
|
||||||
|
|
||||||
|
zz2.add(zz2, zz2)
|
||||||
|
|
||||||
|
v.x.subtract(pp, mm)
|
||||||
|
v.y.add(pp, mm)
|
||||||
|
v.z.subtract(zz2, tt2d) // flipped sign
|
||||||
|
v.t.add(zz2, tt2d) // flipped sign
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP1) add_affine(p Point, q AffineCached) ProjectiveP1 {
|
||||||
|
mut ypx := Element{}
|
||||||
|
mut ymx := Element{}
|
||||||
|
mut pp := Element{}
|
||||||
|
mut mm := Element{}
|
||||||
|
mut tt2d := Element{}
|
||||||
|
mut z2 := Element{}
|
||||||
|
|
||||||
|
ypx.add(p.y, p.x)
|
||||||
|
ymx.subtract(p.y, p.x)
|
||||||
|
|
||||||
|
pp.multiply(&ypx, q.ypx)
|
||||||
|
mm.multiply(&ymx, q.ymx)
|
||||||
|
tt2d.multiply(p.t, q.t2d)
|
||||||
|
|
||||||
|
z2.add(p.z, p.z)
|
||||||
|
|
||||||
|
v.x.subtract(pp, mm)
|
||||||
|
v.y.add(pp, mm)
|
||||||
|
v.z.add(z2, tt2d)
|
||||||
|
v.t.subtract(z2, tt2d)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP1) sub_affine(p Point, q AffineCached) ProjectiveP1 {
|
||||||
|
mut ypx := Element{}
|
||||||
|
mut ymx := Element{}
|
||||||
|
mut pp := Element{}
|
||||||
|
mut mm := Element{}
|
||||||
|
mut tt2d := Element{}
|
||||||
|
mut z2 := Element{}
|
||||||
|
|
||||||
|
ypx.add(p.y, p.x)
|
||||||
|
ymx.subtract(p.y, p.x)
|
||||||
|
|
||||||
|
pp.multiply(ypx, q.ymx) // flipped sign
|
||||||
|
mm.multiply(ymx, q.ypx) // flipped sign
|
||||||
|
tt2d.multiply(p.t, q.t2d)
|
||||||
|
|
||||||
|
z2.add(p.z, p.z)
|
||||||
|
|
||||||
|
v.x.subtract(pp, mm)
|
||||||
|
v.y.add(pp, mm)
|
||||||
|
v.z.subtract(z2, tt2d) // flipped sign
|
||||||
|
v.t.add(z2, tt2d) // flipped sign
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doubling.
|
||||||
|
|
||||||
|
fn (mut v ProjectiveP1) double(p ProjectiveP2) ProjectiveP1 {
|
||||||
|
// var xx, yy, zz2, xplusysq edwards25519.Element
|
||||||
|
mut xx := Element{}
|
||||||
|
mut yy := Element{}
|
||||||
|
mut zz2 := Element{}
|
||||||
|
mut xplusysq := Element{}
|
||||||
|
|
||||||
|
xx.square(p.x)
|
||||||
|
yy.square(p.y)
|
||||||
|
zz2.square(p.z)
|
||||||
|
zz2.add(zz2, zz2)
|
||||||
|
xplusysq.add(p.x, p.y)
|
||||||
|
xplusysq.square(xplusysq)
|
||||||
|
|
||||||
|
v.y.add(yy, xx)
|
||||||
|
v.z.subtract(yy, xx)
|
||||||
|
|
||||||
|
v.x.subtract(xplusysq, v.y)
|
||||||
|
v.t.subtract(zz2, v.z)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negation.
|
||||||
|
|
||||||
|
// `negate` sets v = -p, and returns v.
|
||||||
|
pub fn (mut v Point) negate(p Point) Point {
|
||||||
|
check_initialized(p)
|
||||||
|
v.x.negate(p.x)
|
||||||
|
v.y.set(p.y)
|
||||||
|
v.z.set(p.z)
|
||||||
|
v.t.negate(p.t)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// `equal` returns 1 if v is equivalent to u, and 0 otherwise.
|
||||||
|
pub fn (mut v Point) equal(u Point) int {
|
||||||
|
check_initialized(v, u)
|
||||||
|
|
||||||
|
mut t1 := Element{}
|
||||||
|
mut t2 := Element{}
|
||||||
|
mut t3 := Element{}
|
||||||
|
mut t4 := Element{}
|
||||||
|
|
||||||
|
t1.multiply(v.x, u.z)
|
||||||
|
t2.multiply(u.x, v.z)
|
||||||
|
t3.multiply(v.y, u.z)
|
||||||
|
t4.multiply(u.y, v.z)
|
||||||
|
|
||||||
|
return t1.equal(t2) & t3.equal(t4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant-time operations
|
||||||
|
|
||||||
|
// selected sets v to a if cond == 1 and to b if cond == 0.
|
||||||
|
fn (mut v ProjectiveCached) selected(a ProjectiveCached, b ProjectiveCached, cond int) ProjectiveCached {
|
||||||
|
v.ypx.selected(a.ypx, b.ypx, cond)
|
||||||
|
v.ymx.selected(a.ymx, b.ymx, cond)
|
||||||
|
v.z.selected(a.z, b.z, cond)
|
||||||
|
v.t2d.selected(a.t2d, b.t2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// selected sets v to a if cond == 1 and to b if cond == 0.
|
||||||
|
fn (mut v AffineCached) selected(a AffineCached, b AffineCached, cond int) AffineCached {
|
||||||
|
v.ypx.selected(a.ypx, b.ypx, cond)
|
||||||
|
v.ymx.selected(a.ymx, b.ymx, cond)
|
||||||
|
v.t2d.selected(a.t2d, b.t2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// cond_neg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||||
|
fn (mut v ProjectiveCached) cond_neg(cond int) ProjectiveCached {
|
||||||
|
mut el := Element{}
|
||||||
|
v.ypx.swap(mut v.ymx, cond)
|
||||||
|
v.t2d.selected(el.negate(v.t2d), v.t2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// cond_neg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||||
|
fn (mut v AffineCached) cond_neg(cond int) AffineCached {
|
||||||
|
mut el := Element{}
|
||||||
|
v.ypx.swap(mut v.ymx, cond)
|
||||||
|
v.t2d.selected(el.negate(v.t2d), v.t2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_on_curve(points ...Point) bool {
|
||||||
|
for p in points {
|
||||||
|
mut xx := Element{}
|
||||||
|
mut yy := Element{}
|
||||||
|
mut zz := Element{}
|
||||||
|
mut zzzz := Element{}
|
||||||
|
xx.square(p.x)
|
||||||
|
yy.square(p.y)
|
||||||
|
zz.square(p.z)
|
||||||
|
zzzz.square(zz)
|
||||||
|
// -x² + y² = 1 + dx²y²
|
||||||
|
// -(X/Z)² + (Y/Z)² = 1 + d(X/Z)²(Y/Z)²
|
||||||
|
// (-X² + Y²)/Z² = 1 + (dX²Y²)/Z⁴
|
||||||
|
// (-X² + Y²)*Z² = Z⁴ + dX²Y²
|
||||||
|
mut lhs := Element{}
|
||||||
|
mut rhs := Element{}
|
||||||
|
lhs.subtract(yy, xx)
|
||||||
|
lhs.multiply(lhs, zz)
|
||||||
|
rhs.multiply(edwards25519.d_const, xx)
|
||||||
|
rhs.multiply(rhs, yy)
|
||||||
|
rhs.add(rhs, zzzz)
|
||||||
|
|
||||||
|
if lhs.equal(rhs) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if lhs.equal(rhs) != 1 {
|
||||||
|
lg.error('X, Y, and Z do not specify a point on the curve\nX = $p.x \nY = $p.y\nZ = $p.z')
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// xy = T/Z
|
||||||
|
lhs.multiply(p.x, p.y)
|
||||||
|
rhs.multiply(p.z, p.t)
|
||||||
|
/*
|
||||||
|
if lhs.equal(rhs) != 1 {
|
||||||
|
lg.error('point $i is not valid\nX = $p.x\nY = $p.y\nZ = $p.z')
|
||||||
|
}*/
|
||||||
|
if lhs.equal(rhs) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
import encoding.hex
|
||||||
|
|
||||||
|
const zero_point = Point{fe_zero, fe_zero, fe_zero, fe_zero}
|
||||||
|
|
||||||
|
fn test_invalid_encodings() ? {
|
||||||
|
// An invalid point, that also happens to have y > p.
|
||||||
|
invalid := 'efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f'
|
||||||
|
inv_bytes := hex.decode(invalid) or { panic(err) }
|
||||||
|
mut p := new_generator_point()
|
||||||
|
|
||||||
|
out := p.set_bytes(inv_bytes) or { edwards25519.zero_point }
|
||||||
|
assert out == edwards25519.zero_point
|
||||||
|
// assert p.equal(bgp) == 1 //not makes sense when error
|
||||||
|
|
||||||
|
assert check_on_curve(p) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_add_sub_neg_on_basepoint() ? {
|
||||||
|
bgp := new_generator_point()
|
||||||
|
mut idp := new_identity_point()
|
||||||
|
mut checklhs := Point{}
|
||||||
|
mut checkrhs := Point{}
|
||||||
|
|
||||||
|
checklhs.add(bgp, bgp)
|
||||||
|
|
||||||
|
mut proj_p1 := ProjectiveP1{}
|
||||||
|
mut proj_p2 := ProjectiveP2{}
|
||||||
|
|
||||||
|
tmp_p2 := proj_p2.from_p3(bgp)
|
||||||
|
tmp_p1 := proj_p1.double(tmp_p2)
|
||||||
|
checkrhs.from_p1(tmp_p1)
|
||||||
|
|
||||||
|
assert checklhs.equal(checkrhs) == 1
|
||||||
|
assert check_on_curve(checklhs, checkrhs) == true
|
||||||
|
|
||||||
|
checklhs.subtract(bgp, bgp)
|
||||||
|
mut p0 := Point{}
|
||||||
|
bneg := p0.negate(bgp)
|
||||||
|
checkrhs.add(bgp, bneg)
|
||||||
|
|
||||||
|
assert checklhs.equal(checkrhs) == 1
|
||||||
|
assert idp.equal(checklhs) == 1
|
||||||
|
assert idp.equal(checkrhs) == 1
|
||||||
|
assert check_on_curve(checklhs, checkrhs, bneg) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NonCanonicalTest {
|
||||||
|
name string
|
||||||
|
encoding string
|
||||||
|
canonical string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_non_canonical_points() ? {
|
||||||
|
tests := [
|
||||||
|
// Points with x = 0 and the sign bit set. With x = 0 the curve equation
|
||||||
|
// gives y² = 1, so y = ±1. 1 has two valid encodings.
|
||||||
|
NonCanonicalTest{'y=1,sign-', '0100000000000000000000000000000000000000000000000000000000000080', '0100000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+1,sign-', 'eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0100000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p-1,sign-', 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f'},
|
||||||
|
// Non-canonical y encodings with values 2²⁵⁵-19 (p) to 2²⁵⁵-1 (p+18).
|
||||||
|
NonCanonicalTest{'y=p,sign+', 'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0000000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p,sign-', 'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0000000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
NonCanonicalTest{'y=p+1,sign+', 'eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0100000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
// "y=p+1,sign-" is already tested above.
|
||||||
|
// p+2 is not a valid y-coordinate.
|
||||||
|
NonCanonicalTest{'y=p+3,sign+', 'f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0300000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+3,sign-', 'f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0300000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
NonCanonicalTest{'y=p+4,sign+', 'f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0400000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+4,sign-', 'f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0400000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
NonCanonicalTest{'y=p+5,sign+', 'f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0500000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+5,sign-', 'f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0500000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
NonCanonicalTest{'y=p+6,sign+', 'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0600000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+6,sign-', 'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0600000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
// p+7 is not a valid y-coordinate.
|
||||||
|
// p+8 is not a valid y-coordinate.
|
||||||
|
NonCanonicalTest{'y=p+9,sign+', 'f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0900000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+9,sign-', 'f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0900000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
NonCanonicalTest{'y=p+10,sign+', 'f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0a00000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+10,sign-', 'f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0a00000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
// p+11 is not a valid y-coordinate.
|
||||||
|
// p+12 is not a valid y-coordinate.
|
||||||
|
// p+13 is not a valid y-coordinate.
|
||||||
|
NonCanonicalTest{'y=p+14,sign+', 'fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0e00000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+14,sign-', 'fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0e00000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
NonCanonicalTest{'y=p+15,sign+', 'fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0f00000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+15,sign-', 'fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0f00000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
NonCanonicalTest{'y=p+16,sign+', 'fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '1000000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+16,sign-', 'fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '1000000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
// p+17 is not a valid y-coordinate.
|
||||||
|
NonCanonicalTest{'y=p+18,sign+', 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '1200000000000000000000000000000000000000000000000000000000000000'},
|
||||||
|
NonCanonicalTest{'y=p+18,sign-', 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '1200000000000000000000000000000000000000000000000000000000000080'},
|
||||||
|
]
|
||||||
|
for tt in tests {
|
||||||
|
// t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// p1, err := new(Point).SetBytes(decodeHex(tt.encoding))
|
||||||
|
mut p1 := Point{}
|
||||||
|
p1.set_bytes(hex.decode(tt.encoding) ?) ?
|
||||||
|
|
||||||
|
// p2, err := new(Point).SetBytes(decodeHex(tt.canonical))
|
||||||
|
mut p2 := Point{}
|
||||||
|
p2.set_bytes(hex.decode(tt.canonical) ?) ?
|
||||||
|
|
||||||
|
assert p1.equal(p2) == 1
|
||||||
|
assert p1.bytes() == p2.bytes()
|
||||||
|
assert hex.encode(p1.bytes()) == tt.canonical // NEED FIX!
|
||||||
|
|
||||||
|
assert check_on_curve(p1, p2) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_generator() {
|
||||||
|
// These are the coordinates of B from RFC 8032, Section 5.1, converted to
|
||||||
|
// little endian hex.
|
||||||
|
x := '1ad5258f602d56c9b2a7259560c72c695cdcd6fd31e2a4c0fe536ecdd3366921'
|
||||||
|
y := '5866666666666666666666666666666666666666666666666666666666666666'
|
||||||
|
mut b := new_generator_point()
|
||||||
|
|
||||||
|
assert hex.encode(b.x.bytes()) == x
|
||||||
|
assert hex.encode(b.y.bytes()) == y
|
||||||
|
assert b.z.equal(fe_one) == 1
|
||||||
|
// Check that t is correct.
|
||||||
|
assert check_on_curve(b) == true
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,106 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
fn check_aliasing_onearg(f fn (mut v Scalar, x Scalar) Scalar, mut v Scalar, x Scalar) bool {
|
||||||
|
x1, mut v1 := x, x
|
||||||
|
|
||||||
|
// Calculate a reference f(x) without aliasing.
|
||||||
|
mut out := f(mut v, x)
|
||||||
|
if out != v || !is_reduced(out) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test aliasing the argument and the receiver.
|
||||||
|
out2 := f(mut v1, v1)
|
||||||
|
if out2 != v1 || v1 != v || !is_reduced(out2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the arguments was not modified.
|
||||||
|
return x == x1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn negate_aliasing(mut v Scalar, x Scalar) Scalar {
|
||||||
|
// mut t := v
|
||||||
|
return v.negate(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_check_aliasing_oneargs() ? {
|
||||||
|
x := generate_notzero_scalar(10) ?
|
||||||
|
mut v := generate_notzero_scalar(10) ?
|
||||||
|
out := check_aliasing_onearg(negate_aliasing, mut v, x)
|
||||||
|
assert out == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multiply_aliasing(mut v Scalar, x Scalar, y Scalar) Scalar {
|
||||||
|
return v.multiply(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_aliasing(mut v Scalar, x Scalar, y Scalar) Scalar {
|
||||||
|
return v.add(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subtract_aliasing(mut v Scalar, x Scalar, y Scalar) Scalar {
|
||||||
|
return v.subtract(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_check_aliasing_twoargs() ? {
|
||||||
|
fn_with_twoargs := [add_aliasing, multiply_aliasing, subtract_aliasing]
|
||||||
|
for f in fn_with_twoargs {
|
||||||
|
mut v := generate_notzero_scalar(10) ?
|
||||||
|
x := generate_notzero_scalar(10) ?
|
||||||
|
y := generate_notzero_scalar(10) ?
|
||||||
|
out := check_aliasing_twoargs(f, mut v, x, y)
|
||||||
|
assert out == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_aliasing_twoargs(f fn (mut v Scalar, x Scalar, y Scalar) Scalar, mut v Scalar, x Scalar, y Scalar) bool {
|
||||||
|
x1, y1, mut v1 := x, y, Scalar{}
|
||||||
|
|
||||||
|
// Calculate a reference f(x, y) without aliasing.
|
||||||
|
mut out := f(mut v, x, y)
|
||||||
|
if out != v || !is_reduced(out) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test aliasing the first argument and the receiver.
|
||||||
|
v1 = x
|
||||||
|
out2 := f(mut v1, v1, y)
|
||||||
|
if out2 != v1 || v1 != v || !is_reduced(out2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Test aliasing the second argument and the receiver.
|
||||||
|
v1 = y
|
||||||
|
out3 := f(mut v1, x, v1)
|
||||||
|
if out3 != v1 || v1 != v || !is_reduced(out3) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate a reference f(x, x) without aliasing.
|
||||||
|
out4 := f(mut v, x, x)
|
||||||
|
if out4 != v || !is_reduced(out4) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test aliasing the first argument and the receiver.
|
||||||
|
v1 = x
|
||||||
|
out5 := f(mut v1, v1, x)
|
||||||
|
if out5 != v1 || v1 != v || !is_reduced(out5) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Test aliasing the second argument and the receiver.
|
||||||
|
v1 = x
|
||||||
|
out6 := f(mut v1, x, v1)
|
||||||
|
if out6 != v1 || v1 != v || !is_reduced(out6) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Test aliasing both arguments and the receiver.
|
||||||
|
v1 = x
|
||||||
|
out7 := f(mut v1, v1, v1)
|
||||||
|
if out7 != v1 || v1 != v || !is_reduced(out7) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the arguments were not modified.
|
||||||
|
return x == x1 && y == y1
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
import os
|
||||||
|
import rand
|
||||||
|
import encoding.hex
|
||||||
|
import math.big
|
||||||
|
|
||||||
|
const github_job = os.getenv('GITHUB_JOB')
|
||||||
|
|
||||||
|
fn testsuite_begin() {
|
||||||
|
if edwards25519.github_job != '' {
|
||||||
|
// ensure that the CI does not run flaky tests:
|
||||||
|
rand.seed([u32(0xffff24), 0xabcd])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_equal() {
|
||||||
|
assert sc_one.equal(sc_minus_one) != 1
|
||||||
|
|
||||||
|
assert sc_minus_one.equal(sc_minus_one) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_non_adjacent_form() {
|
||||||
|
mut s := Scalar{
|
||||||
|
s: [byte(0x1a), 0x0e, 0x97, 0x8a, 0x90, 0xf6, 0x62, 0x2d, 0x37, 0x47, 0x02, 0x3f, 0x8a,
|
||||||
|
0xd8, 0x26, 0x4d, 0xa7, 0x58, 0xaa, 0x1b, 0x88, 0xe0, 0x40, 0xd1, 0x58, 0x9e, 0x7b,
|
||||||
|
0x7f, 0x23, 0x76, 0xef, 0x09]!
|
||||||
|
}
|
||||||
|
expected_naf := [i8(0), 13, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, -11,
|
||||||
|
0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 3, 0, 0,
|
||||||
|
0, 0, 11, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 9, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, -15, 0, 0, 0, 0, -7, 0, 0,
|
||||||
|
0, 0, -9, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, -11, 0, 0, 0,
|
||||||
|
0, -7, 0, 0, 0, 0, -13, 0, 0, 0, 0, 11, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
|
-15, 0, 0, 0, 0, 1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 13, 0, 0,
|
||||||
|
0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 7, 0, 0, 0, 0, 0, -15, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 0, 0, 0]
|
||||||
|
|
||||||
|
snaf := s.non_adjacent_form(5)
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
assert expected_naf[i] == snaf[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addlike_subneg(x Scalar, y Scalar) bool {
|
||||||
|
// Compute t1 = x - y
|
||||||
|
mut t1 := Scalar{}
|
||||||
|
t1.subtract(x, y)
|
||||||
|
|
||||||
|
// Compute t2 = -y + x
|
||||||
|
mut t2 := Scalar{}
|
||||||
|
t2.negate(y)
|
||||||
|
t2.add(t2, x)
|
||||||
|
|
||||||
|
return t1 == t2 && is_reduced(t1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_add_like_subneg() {
|
||||||
|
for i in 0 .. 15 {
|
||||||
|
x := generate_scalar(1000) or { panic(err.msg) }
|
||||||
|
y := generate_scalar(1000) or { panic(err.msg) }
|
||||||
|
assert addlike_subneg(x, y) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fg(sc Scalar) bool {
|
||||||
|
return is_reduced(sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_generate() ? {
|
||||||
|
for i in 0 .. 15 {
|
||||||
|
sc := generate_scalar(1000) or { panic(err.msg) }
|
||||||
|
|
||||||
|
assert fg(sc) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
fn test_scalar_set_canonical_bytes() ? {
|
||||||
|
for i in 0 .. 10 {
|
||||||
|
mut buf := rand.bytes(32) or { panic(err.msg) }
|
||||||
|
mut sc := generate_scalar(1000) or { panic(err.msg) }
|
||||||
|
buf[buf.len - 1] &= (1 << 4) - 1
|
||||||
|
sc = sc.set_canonical_bytes(buf) or { panic(err.msg) }
|
||||||
|
|
||||||
|
assert buf[..] == sc.bytes()
|
||||||
|
assert is_reduced(sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_set_canonical_bytes_round_trip() ? {
|
||||||
|
for i in 0 .. 10 {
|
||||||
|
mut sc1 := generate_scalar(2) ?
|
||||||
|
mut sc2 := generate_scalar(6) ?
|
||||||
|
sc2.set_canonical_bytes(sc1.bytes()) or { panic(err.msg) }
|
||||||
|
|
||||||
|
assert sc1 == sc2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
sc_error = Scalar{
|
||||||
|
s: [32]byte{init: (byte(-1))}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fn test_scalar_set_canonical_bytes_on_noncanonical_value() ? {
|
||||||
|
mut b := sc_minus_one.s
|
||||||
|
b[31] += 1
|
||||||
|
|
||||||
|
mut s := sc_one
|
||||||
|
out := s.set_canonical_bytes(b[..]) or { edwards25519.sc_error } // set_canonical_bytes shouldn't worked on a non-canonical value"
|
||||||
|
assert out == edwards25519.sc_error
|
||||||
|
assert s == sc_one
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_set_uniform_bytes() ? {
|
||||||
|
// mod, _ := new(big.Integer).SetString("27742317777372353535851937790883648493", 10)
|
||||||
|
mut mod := big.integer_from_string('27742317777372353535851937790883648493') ?
|
||||||
|
// mod.Add(mod, new(big.Integer).Lsh(big.NewInt(1), 252))
|
||||||
|
mod = mod + big.integer_from_i64(1).lshift(252)
|
||||||
|
|
||||||
|
mut sc := generate_scalar(100) ?
|
||||||
|
inp := rand.bytes(64) ?
|
||||||
|
|
||||||
|
sc.set_uniform_bytes(inp[..]) ?
|
||||||
|
assert is_reduced(sc) == true
|
||||||
|
|
||||||
|
scbig := bigint_from_le_bytes(sc.s[..])
|
||||||
|
inbig := bigint_from_le_bytes(inp)
|
||||||
|
// return inbig.Mod(inbig, mod).Cmp(scbig) == 0
|
||||||
|
_, m := inbig.div_mod(mod)
|
||||||
|
assert m.abs_cmp(scbig) == 0 // NEED FIX
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bigint_from_le_bytes(b []byte) big.Integer {
|
||||||
|
mut bc := b.clone()
|
||||||
|
buf := swap_endianness(mut bc) // WITHOUT THIS, some test would fail
|
||||||
|
bg := big.integer_from_bytes(buf)
|
||||||
|
return bg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_set_bytes_with_clamping() {
|
||||||
|
// Generated with libsodium.js 1.0.18 crypto_scalarmult_ed25519_base.
|
||||||
|
/*
|
||||||
|
random := "633d368491364dc9cd4c1bf891b1d59460face1644813240a313e61f2c88216e"
|
||||||
|
s, _ := new(Scalar).SetBytesWithClamping(decodeHex(random))
|
||||||
|
p := new(Point).ScalarBaseMult(s)
|
||||||
|
want := "1d87a9026fd0126a5736fe1628c95dd419172b5b618457e041c9c861b2494a94"
|
||||||
|
if got := hex.EncodeToString(p.Bytes()); got != want {
|
||||||
|
t.Errorf("random: got %q, want %q", got, want)
|
||||||
|
}*/
|
||||||
|
random := '633d368491364dc9cd4c1bf891b1d59460face1644813240a313e61f2c88216e'
|
||||||
|
random_bytes := hex.decode(random) or { panic(err.msg) }
|
||||||
|
|
||||||
|
mut s0 := Scalar{}
|
||||||
|
s0.set_bytes_with_clamping(random_bytes) or { panic(err.msg) }
|
||||||
|
|
||||||
|
mut p0 := Point{}
|
||||||
|
p0.scalar_base_mult(mut s0)
|
||||||
|
|
||||||
|
want0 := '1d87a9026fd0126a5736fe1628c95dd419172b5b618457e041c9c861b2494a94'
|
||||||
|
got0 := hex.encode(p0.bytes())
|
||||||
|
|
||||||
|
assert got0 == want0
|
||||||
|
|
||||||
|
zero := '0000000000000000000000000000000000000000000000000000000000000000'
|
||||||
|
mut s1 := Scalar{}
|
||||||
|
zero_bytes := hex.decode(zero) or { panic(err.msg) }
|
||||||
|
s1.set_bytes_with_clamping(zero_bytes) or { panic(err.msg) }
|
||||||
|
mut p1 := Point{}
|
||||||
|
p1.scalar_base_mult(mut s1)
|
||||||
|
|
||||||
|
want1 := '693e47972caf527c7883ad1b39822f026f47db2ab0e1919955b8993aa04411d1'
|
||||||
|
got1 := hex.encode(p1.bytes())
|
||||||
|
assert want1 == got1
|
||||||
|
|
||||||
|
one := 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
||||||
|
mut s2 := Scalar{}
|
||||||
|
mut one_bytes := hex.decode(one) or { panic(err.msg) }
|
||||||
|
s2.set_bytes_with_clamping(one_bytes) or { panic(err.msg) }
|
||||||
|
mut p2 := Point{}
|
||||||
|
p2.scalar_base_mult(mut s2)
|
||||||
|
|
||||||
|
want2 := '12e9a68b73fd5aacdbcaf3e88c46fea6ebedb1aa84eed1842f07f8edab65e3a7'
|
||||||
|
got2 := hex.encode(p2.bytes())
|
||||||
|
|
||||||
|
assert want2 == got2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_multiply_distributes_over_add() ? {
|
||||||
|
x := generate_scalar(100) ?
|
||||||
|
y := generate_scalar(100) ?
|
||||||
|
z := generate_scalar(100) ?
|
||||||
|
|
||||||
|
// Compute t1 = (x+y)*z
|
||||||
|
mut t1 := Scalar{}
|
||||||
|
t1.add(x, y)
|
||||||
|
t1.multiply(t1, z)
|
||||||
|
|
||||||
|
// Compute t2 = x*z + y*z
|
||||||
|
mut t2 := Scalar{}
|
||||||
|
mut t3 := Scalar{}
|
||||||
|
t2.multiply(x, z)
|
||||||
|
t3.multiply(y, z)
|
||||||
|
t2.add(t2, t3)
|
||||||
|
|
||||||
|
assert t1 == t2 && is_reduced(t1) && is_reduced(t3)
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
import sync
|
||||||
|
|
||||||
|
struct BasepointTablePrecomp {
|
||||||
|
mut:
|
||||||
|
table []AffineLookupTable
|
||||||
|
initonce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// `basepoint_table` is a set of 32 affineLookupTables, where table i is generated
|
||||||
|
// from 256i * basepoint. It is precomputed the first time it's used.
|
||||||
|
fn basepoint_table() []AffineLookupTable {
|
||||||
|
mut bpt := &BasepointTablePrecomp{
|
||||||
|
table: []AffineLookupTable{len: 32}
|
||||||
|
initonce: sync.new_once()
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaced to use do_with_param on newest sync lib
|
||||||
|
/*
|
||||||
|
bpt.initonce.do(fn [mut bpt] () {
|
||||||
|
mut p := new_generator_point()
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
bpt.table[i].from_p3(p)
|
||||||
|
for j := 0; j < 8; j++ {
|
||||||
|
p.add(p, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})*/
|
||||||
|
bpt.initonce.do_with_param(fn (mut o BasepointTablePrecomp) {
|
||||||
|
mut p := new_generator_point()
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
o.table[i].from_p3(p)
|
||||||
|
for j := 0; j < 8; j++ {
|
||||||
|
p.add(p, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, bpt)
|
||||||
|
return bpt.table
|
||||||
|
}
|
||||||
|
|
||||||
|
// `scalar_base_mult` sets v = x * B, where B is the canonical generator, and
|
||||||
|
// returns v.
|
||||||
|
//
|
||||||
|
// The scalar multiplication is done in constant time.
|
||||||
|
pub fn (mut v Point) scalar_base_mult(mut x Scalar) Point {
|
||||||
|
mut bpt_table := basepoint_table()
|
||||||
|
|
||||||
|
// Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i )
|
||||||
|
// as described in the Ed25519 paper
|
||||||
|
//
|
||||||
|
// Group even and odd coefficients
|
||||||
|
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||||
|
// + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B
|
||||||
|
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||||
|
// + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B)
|
||||||
|
//
|
||||||
|
// We use a lookup table for each i to get x_i*16^(2*i)*B
|
||||||
|
// and do four doublings to multiply by 16.
|
||||||
|
digits := x.signed_radix16()
|
||||||
|
|
||||||
|
mut multiple := AffineCached{}
|
||||||
|
mut tmp1 := ProjectiveP1{}
|
||||||
|
mut tmp2 := ProjectiveP2{}
|
||||||
|
|
||||||
|
// Accumulate the odd components first
|
||||||
|
v.set(new_identity_point())
|
||||||
|
for i := 1; i < 64; i += 2 {
|
||||||
|
bpt_table[i / 2].select_into(mut multiple, digits[i])
|
||||||
|
tmp1.add_affine(v, multiple)
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply by 16
|
||||||
|
tmp2.from_p3(v) // tmp2 = v in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 2*v in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 2*v in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 4*v in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 4*v in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 8*v in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 8*v in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 16*v in P1xP1 coords
|
||||||
|
v.from_p1(tmp1) // now v = 16*(odd components)
|
||||||
|
|
||||||
|
// Accumulate the even components
|
||||||
|
for j := 0; j < 64; j += 2 {
|
||||||
|
bpt_table[j / 2].select_into(mut multiple, digits[j])
|
||||||
|
tmp1.add_affine(v, multiple)
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// `scalar_mult` sets v = x * q, and returns v.
|
||||||
|
//
|
||||||
|
// The scalar multiplication is done in constant time.
|
||||||
|
pub fn (mut v Point) scalar_mult(mut x Scalar, q Point) Point {
|
||||||
|
check_initialized(q)
|
||||||
|
|
||||||
|
mut table := ProjLookupTable{}
|
||||||
|
table.from_p3(q)
|
||||||
|
|
||||||
|
// Write x = sum(x_i * 16^i)
|
||||||
|
// so x*Q = sum( Q*x_i*16^i )
|
||||||
|
// = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... )
|
||||||
|
// <------compute inside out---------
|
||||||
|
//
|
||||||
|
// We use the lookup table to get the x_i*Q values
|
||||||
|
// and do four doublings to compute 16*Q
|
||||||
|
digits := x.signed_radix16()
|
||||||
|
|
||||||
|
// Unwrap first loop iteration to save computing 16*identity
|
||||||
|
mut multiple := ProjectiveCached{}
|
||||||
|
mut tmp1 := ProjectiveP1{}
|
||||||
|
mut tmp2 := ProjectiveP2{}
|
||||||
|
table.select_into(mut multiple, digits[63])
|
||||||
|
|
||||||
|
v.set(new_identity_point())
|
||||||
|
tmp1.add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords
|
||||||
|
for i := 62; i >= 0; i-- {
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = (prev) in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||||
|
tmp2.from_p1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||||
|
tmp1.double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||||
|
v.from_p1(tmp1) // v = 16*(prev) in P3 coords
|
||||||
|
table.select_into(mut multiple, digits[i])
|
||||||
|
tmp1.add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords
|
||||||
|
}
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BasepointNaftablePrecomp {
|
||||||
|
mut:
|
||||||
|
table NafLookupTable8
|
||||||
|
initonce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basepoint_naf_table() NafLookupTable8 {
|
||||||
|
mut bnft := &BasepointNaftablePrecomp{}
|
||||||
|
bnft.initonce.do_with_param(fn (mut o BasepointNaftablePrecomp) {
|
||||||
|
o.table.from_p3(new_generator_point())
|
||||||
|
}, bnft)
|
||||||
|
return bnft.table
|
||||||
|
}
|
||||||
|
|
||||||
|
// `vartime_double_scalar_base_mult` sets v = a * A + b * B, where B is the canonical
|
||||||
|
// generator, and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends on the inputs.
|
||||||
|
pub fn (mut v Point) vartime_double_scalar_base_mult(xa Scalar, aa Point, xb Scalar) Point {
|
||||||
|
check_initialized(aa)
|
||||||
|
|
||||||
|
// Similarly to the single variable-base approach, we compute
|
||||||
|
// digits and use them with a lookup table. However, because
|
||||||
|
// we are allowed to do variable-time operations, we don't
|
||||||
|
// need constant-time lookups or constant-time digit
|
||||||
|
// computations.
|
||||||
|
//
|
||||||
|
// So we use a non-adjacent form of some width w instead of
|
||||||
|
// radix 16. This is like a binary representation (one digit
|
||||||
|
// for each binary place) but we allow the digits to grow in
|
||||||
|
// magnitude up to 2^{w-1} so that the nonzero digits are as
|
||||||
|
// sparse as possible. Intuitively, this "condenses" the
|
||||||
|
// "mass" of the scalar onto sparse coefficients (meaning
|
||||||
|
// fewer additions).
|
||||||
|
|
||||||
|
mut bp_naftable := basepoint_naf_table()
|
||||||
|
mut atable := NafLookupTable5{}
|
||||||
|
atable.from_p3(aa)
|
||||||
|
// Because the basepoint is fixed, we can use a wider NAF
|
||||||
|
// corresponding to a bigger table.
|
||||||
|
mut a := xa
|
||||||
|
mut b := xb
|
||||||
|
anaf := a.non_adjacent_form(5)
|
||||||
|
bnaf := b.non_adjacent_form(8)
|
||||||
|
|
||||||
|
// Find the first nonzero coefficient.
|
||||||
|
mut i := 255
|
||||||
|
for j := i; j >= 0; j-- {
|
||||||
|
if anaf[j] != 0 || bnaf[j] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mut multa := ProjectiveCached{}
|
||||||
|
mut multb := AffineCached{}
|
||||||
|
mut tmp1 := ProjectiveP1{}
|
||||||
|
mut tmp2 := ProjectiveP2{}
|
||||||
|
tmp2.zero()
|
||||||
|
|
||||||
|
// Move from high to low bits, doubling the accumulator
|
||||||
|
// at each iteration and checking whether there is a nonzero
|
||||||
|
// coefficient to look up a multiple of.
|
||||||
|
for ; i >= 0; i-- {
|
||||||
|
tmp1.double(tmp2)
|
||||||
|
|
||||||
|
// Only update v if we have a nonzero coeff to add in.
|
||||||
|
if anaf[i] > 0 {
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
atable.select_into(mut multa, anaf[i])
|
||||||
|
tmp1.add(v, multa)
|
||||||
|
} else if anaf[i] < 0 {
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
atable.select_into(mut multa, -anaf[i])
|
||||||
|
tmp1.sub(v, multa)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bnaf[i] > 0 {
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
bp_naftable.select_into(mut multb, bnaf[i])
|
||||||
|
tmp1.add_affine(v, multb)
|
||||||
|
} else if bnaf[i] < 0 {
|
||||||
|
v.from_p1(tmp1)
|
||||||
|
bp_naftable.select_into(mut multb, -bnaf[i])
|
||||||
|
tmp1.sub_affine(v, multb)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp2.from_p1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.from_p2(tmp2)
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
const (
|
||||||
|
dalek_scalar = Scalar{[byte(219), 106, 114, 9, 174, 249, 155, 89, 69, 203, 201, 93, 92, 116,
|
||||||
|
234, 187, 78, 115, 103, 172, 182, 98, 62, 103, 187, 136, 13, 100, 248, 110, 12, 4]!}
|
||||||
|
dsc_basepoint = [byte(0xf4), 0xef, 0x7c, 0xa, 0x34, 0x55, 0x7b, 0x9f, 0x72, 0x3b, 0xb6, 0x1e,
|
||||||
|
0xf9, 0x46, 0x9, 0x91, 0x1c, 0xb9, 0xc0, 0x6c, 0x17, 0x28, 0x2d, 0x8b, 0x43, 0x2b, 0x5,
|
||||||
|
0x18, 0x6a, 0x54, 0x3e, 0x48]
|
||||||
|
)
|
||||||
|
|
||||||
|
fn dalek_scalar_basepoint() Point {
|
||||||
|
mut p := Point{}
|
||||||
|
p.set_bytes(edwards25519.dsc_basepoint) or { panic(err.msg) }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_mult_small_scalars() {
|
||||||
|
mut z := Scalar{}
|
||||||
|
mut p := Point{}
|
||||||
|
mut b := new_generator_point()
|
||||||
|
mut i := new_identity_point()
|
||||||
|
p.scalar_mult(mut z, b)
|
||||||
|
|
||||||
|
assert i.equal(p) == 1
|
||||||
|
assert check_on_curve(p) == true
|
||||||
|
|
||||||
|
z = Scalar{[byte(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]!}
|
||||||
|
p.scalar_mult(mut z, b)
|
||||||
|
|
||||||
|
assert b.equal(p) == 1
|
||||||
|
assert check_on_curve(p) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_mult_vs_dalek() {
|
||||||
|
mut p := Point{}
|
||||||
|
mut b := new_generator_point()
|
||||||
|
mut dsc := edwards25519.dalek_scalar
|
||||||
|
p.scalar_mult(mut dsc, b)
|
||||||
|
mut ds := dalek_scalar_basepoint()
|
||||||
|
assert ds.equal(p) == 1
|
||||||
|
|
||||||
|
assert check_on_curve(p) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_base_mult_vs_dalek() {
|
||||||
|
mut p := Point{}
|
||||||
|
mut dsc := edwards25519.dalek_scalar
|
||||||
|
p.scalar_base_mult(mut dsc)
|
||||||
|
mut ds := dalek_scalar_basepoint()
|
||||||
|
assert ds.equal(p) == 1
|
||||||
|
|
||||||
|
assert check_on_curve(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_vartime_double_basemult_vs_dalek() {
|
||||||
|
mut p := Point{}
|
||||||
|
mut z := Scalar{}
|
||||||
|
b := new_generator_point()
|
||||||
|
p.vartime_double_scalar_base_mult(edwards25519.dalek_scalar, b, z)
|
||||||
|
|
||||||
|
mut ds := dalek_scalar_basepoint()
|
||||||
|
assert ds.equal(p) == 1
|
||||||
|
assert check_on_curve(p)
|
||||||
|
|
||||||
|
p.vartime_double_scalar_base_mult(z, b, edwards25519.dalek_scalar)
|
||||||
|
|
||||||
|
assert ds.equal(p) == 1
|
||||||
|
assert check_on_curve(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_mult_distributes_over_add() {
|
||||||
|
mut x := generate_scalar(100) or { panic(err.msg) }
|
||||||
|
mut y := generate_scalar(100) or { panic(err.msg) }
|
||||||
|
mut z := Scalar{}
|
||||||
|
|
||||||
|
z.add(x, y)
|
||||||
|
|
||||||
|
mut p := Point{}
|
||||||
|
mut q := Point{}
|
||||||
|
mut r := Point{}
|
||||||
|
mut check := Point{}
|
||||||
|
mut b := new_generator_point()
|
||||||
|
|
||||||
|
p.scalar_mult(mut x, b)
|
||||||
|
q.scalar_mult(mut y, b)
|
||||||
|
r.scalar_mult(mut z, b)
|
||||||
|
check.add(p, q)
|
||||||
|
|
||||||
|
assert check_on_curve(p, q, r, check) == true
|
||||||
|
assert check.equal(r) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalarmult_non_identity_point() ? {
|
||||||
|
// Check whether p.ScalarMult and q.ScalaBaseMult give the same,
|
||||||
|
// when p and q are originally set to the base point.
|
||||||
|
|
||||||
|
mut x := generate_scalar(5000) ?
|
||||||
|
|
||||||
|
mut p := Point{}
|
||||||
|
mut q := Point{}
|
||||||
|
mut b := new_generator_point()
|
||||||
|
p.set(b)
|
||||||
|
q.set(b)
|
||||||
|
|
||||||
|
p.scalar_mult(mut x, b)
|
||||||
|
q.scalar_base_mult(mut x)
|
||||||
|
|
||||||
|
assert check_on_curve(p, q) == true
|
||||||
|
|
||||||
|
assert p.equal(q) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_basepoint_table_generation() {
|
||||||
|
// The basepoint table is 32 affineLookupTables,
|
||||||
|
// corresponding to (16^2i)*B for table i.
|
||||||
|
bptable := basepoint_table()
|
||||||
|
b := new_generator_point()
|
||||||
|
mut tmp1 := ProjectiveP1{}
|
||||||
|
mut tmp2 := ProjectiveP2{}
|
||||||
|
mut tmp3 := Point{}
|
||||||
|
tmp3.set(b)
|
||||||
|
mut table := []AffineLookupTable{len: 32}
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
// Build the table
|
||||||
|
table[i].from_p3(tmp3)
|
||||||
|
|
||||||
|
// Assert equality with the hardcoded one
|
||||||
|
assert table[i] == bptable[i]
|
||||||
|
|
||||||
|
// Set p = (16^2)*p = 256*p = 2^8*p
|
||||||
|
tmp2.from_p3(tmp3)
|
||||||
|
for j := 0; j < 7; j++ {
|
||||||
|
tmp1.double(tmp2)
|
||||||
|
tmp2.from_p1(tmp1)
|
||||||
|
}
|
||||||
|
tmp1.double(tmp2)
|
||||||
|
tmp3.from_p1(tmp1)
|
||||||
|
|
||||||
|
assert check_on_curve(tmp3) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scalar_mult_matches_base_mult() {
|
||||||
|
mut x := generate_scalar(100) or { panic(err.msg) }
|
||||||
|
b := new_generator_point()
|
||||||
|
mut p := Point{}
|
||||||
|
mut q := Point{}
|
||||||
|
|
||||||
|
p.scalar_mult(mut x, b)
|
||||||
|
q.scalar_base_mult(mut x)
|
||||||
|
|
||||||
|
assert check_on_curve(p, q) == true
|
||||||
|
assert p.equal(q) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_basepoint_naf_table_generation() {
|
||||||
|
mut table := NafLookupTable8{}
|
||||||
|
b := new_generator_point()
|
||||||
|
|
||||||
|
table.from_p3(b)
|
||||||
|
|
||||||
|
bnt := basepoint_naf_table()
|
||||||
|
assert table == bnt
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_vartime_double_scalar_base_mult() {
|
||||||
|
mut x := generate_scalar(100) or { panic(err.msg) }
|
||||||
|
mut y := generate_scalar(100) or { panic(err.msg) }
|
||||||
|
b := new_generator_point()
|
||||||
|
|
||||||
|
mut p := Point{}
|
||||||
|
mut q1 := Point{}
|
||||||
|
mut q2 := Point{}
|
||||||
|
mut check := Point{}
|
||||||
|
|
||||||
|
p.vartime_double_scalar_base_mult(x, b, y)
|
||||||
|
|
||||||
|
q1.scalar_base_mult(mut x)
|
||||||
|
q2.scalar_base_mult(mut y)
|
||||||
|
check.add(q1, q2)
|
||||||
|
|
||||||
|
assert check_on_curve(p, check, q1, q2) == true
|
||||||
|
assert p.equal(check) == 1
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
import crypto.internal.subtle
|
||||||
|
|
||||||
|
// A precomputed lookup table for fixed-base, constant-time scalar muls.
|
||||||
|
struct AffineLookupTable {
|
||||||
|
mut:
|
||||||
|
points [8]AffineCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A dynamic lookup table for variable-base, constant-time scalar muls.
|
||||||
|
struct ProjLookupTable {
|
||||||
|
mut:
|
||||||
|
points [8]ProjectiveCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A dynamic lookup table for variable-base, variable-time scalar muls.
|
||||||
|
struct NafLookupTable5 {
|
||||||
|
mut:
|
||||||
|
points [8]ProjectiveCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precomputed lookup table for fixed-base, variable-time scalar muls.
|
||||||
|
struct NafLookupTable8 {
|
||||||
|
mut:
|
||||||
|
points [64]AffineCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors.
|
||||||
|
|
||||||
|
// Builds a lookup table at runtime. Fast.
|
||||||
|
fn (mut v ProjLookupTable) from_p3(q Point) {
|
||||||
|
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||||
|
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||||
|
v.points[0].from_p3(q)
|
||||||
|
mut tmp_p3 := Point{}
|
||||||
|
mut tmp_p1 := ProjectiveP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
// Compute (i+1)*Q as Q + i*Q and convert to a ProjCached
|
||||||
|
// This is needlessly complicated because the API has explicit
|
||||||
|
// recievers instead of creating stack objects and relying on RVO
|
||||||
|
v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add(q, v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||||
|
fn (mut v AffineLookupTable) from_p3(q Point) {
|
||||||
|
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||||
|
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||||
|
v.points[0].from_p3(q)
|
||||||
|
mut tmp_p3 := Point{}
|
||||||
|
mut tmp_p1 := ProjectiveP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
// Compute (i+1)*Q as Q + i*Q and convert to AffineCached
|
||||||
|
v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add_affine(q, v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a lookup table at runtime. Fast.
|
||||||
|
fn (mut v NafLookupTable5) from_p3(q Point) {
|
||||||
|
// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q
|
||||||
|
// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q
|
||||||
|
v.points[0].from_p3(q)
|
||||||
|
mut q2 := Point{}
|
||||||
|
q2.add(q, q)
|
||||||
|
mut tmp_p3 := Point{}
|
||||||
|
mut tmp_p1 := ProjectiveP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add(q2, v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||||
|
fn (mut v NafLookupTable8) from_p3(q Point) {
|
||||||
|
v.points[0].from_p3(q)
|
||||||
|
mut q2 := Point{}
|
||||||
|
q2.add(q, q)
|
||||||
|
mut tmp_p3 := Point{}
|
||||||
|
mut tmp_p1 := ProjectiveP1{}
|
||||||
|
for i := 0; i < 63; i++ {
|
||||||
|
v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add_affine(q2, v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selectors.
|
||||||
|
|
||||||
|
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||||
|
fn (mut v ProjLookupTable) select_into(mut dest ProjectiveCached, x i8) {
|
||||||
|
// Compute xabs = |x|
|
||||||
|
xmask := x >> 7
|
||||||
|
xabs := byte((x + xmask) ^ xmask)
|
||||||
|
|
||||||
|
dest.zero()
|
||||||
|
for j := 1; j <= 8; j++ {
|
||||||
|
// Set dest = j*Q if |x| = j
|
||||||
|
cond := subtle.constant_time_byte_eq(xabs, byte(j))
|
||||||
|
dest.selected(&v.points[j - 1], dest, cond)
|
||||||
|
}
|
||||||
|
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||||
|
dest.cond_neg(int(xmask & 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||||
|
fn (mut v AffineLookupTable) select_into(mut dest AffineCached, x i8) {
|
||||||
|
// Compute xabs = |x|
|
||||||
|
xmask := x >> 7
|
||||||
|
xabs := byte((x + xmask) ^ xmask)
|
||||||
|
|
||||||
|
dest.zero()
|
||||||
|
for j := 1; j <= 8; j++ {
|
||||||
|
// Set dest = j*Q if |x| = j
|
||||||
|
cond := subtle.constant_time_byte_eq(xabs, byte(j))
|
||||||
|
dest.selected(v.points[j - 1], dest, cond)
|
||||||
|
}
|
||||||
|
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||||
|
dest.cond_neg(int(xmask & 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given odd x with 0 < x < 2^4, return x*Q (in variable time).
|
||||||
|
fn (mut v NafLookupTable5) select_into(mut dest ProjectiveCached, x i8) {
|
||||||
|
dest = v.points[x / 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given odd x with 0 < x < 2^7, return x*Q (in variable time).
|
||||||
|
fn (mut v NafLookupTable8) select_into(mut dest AffineCached, x i8) {
|
||||||
|
dest = v.points[x / 2]
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
module edwards25519
|
||||||
|
|
||||||
|
fn test_proj_lookup_table() {
|
||||||
|
mut table := ProjLookupTable{}
|
||||||
|
b := new_generator_point()
|
||||||
|
table.from_p3(b)
|
||||||
|
|
||||||
|
mut tmp1 := ProjectiveCached{}
|
||||||
|
mut tmp2 := ProjectiveCached{}
|
||||||
|
mut tmp3 := ProjectiveCached{}
|
||||||
|
|
||||||
|
table.select_into(mut tmp1, 6)
|
||||||
|
table.select_into(mut tmp2, -2)
|
||||||
|
table.select_into(mut tmp3, -4)
|
||||||
|
|
||||||
|
// Expect T1 + T2 + T3 = identity
|
||||||
|
mut acc_p1 := ProjectiveP1{}
|
||||||
|
mut acc_p3 := new_identity_point()
|
||||||
|
|
||||||
|
acc_p1.add(acc_p3, tmp1)
|
||||||
|
acc_p3.from_p1(acc_p1)
|
||||||
|
acc_p1.add(acc_p3, tmp2)
|
||||||
|
acc_p3.from_p1(acc_p1)
|
||||||
|
acc_p1.add(acc_p3, tmp3)
|
||||||
|
acc_p3.from_p1(acc_p1)
|
||||||
|
|
||||||
|
assert acc_p3.equal(id_point) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_affine_lookup_table() {
|
||||||
|
mut table := AffineLookupTable{}
|
||||||
|
b := new_generator_point()
|
||||||
|
table.from_p3(b)
|
||||||
|
|
||||||
|
mut tmp1 := AffineCached{}
|
||||||
|
mut tmp2 := AffineCached{}
|
||||||
|
mut tmp3 := AffineCached{}
|
||||||
|
|
||||||
|
table.select_into(mut tmp1, 3)
|
||||||
|
table.select_into(mut tmp2, -7)
|
||||||
|
table.select_into(mut tmp3, 4)
|
||||||
|
// Expect T1 + T2 + T3 = identity
|
||||||
|
|
||||||
|
mut acc_p1 := ProjectiveP1{}
|
||||||
|
mut acc_p3 := new_identity_point()
|
||||||
|
|
||||||
|
acc_p1.add_affine(acc_p3, tmp1)
|
||||||
|
acc_p3.from_p1(acc_p1)
|
||||||
|
acc_p1.add_affine(acc_p3, tmp2)
|
||||||
|
acc_p3.from_p1(acc_p1)
|
||||||
|
acc_p1.add_affine(acc_p3, tmp3)
|
||||||
|
acc_p3.from_p1(acc_p1)
|
||||||
|
|
||||||
|
assert acc_p3.equal(id_point) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_naf_lookup_table5() {
|
||||||
|
mut table := NafLookupTable5{}
|
||||||
|
b := new_generator_point()
|
||||||
|
table.from_p3(b)
|
||||||
|
|
||||||
|
mut tmp1 := ProjectiveCached{}
|
||||||
|
mut tmp2 := ProjectiveCached{}
|
||||||
|
mut tmp3 := ProjectiveCached{}
|
||||||
|
mut tmp4 := ProjectiveCached{}
|
||||||
|
|
||||||
|
table.select_into(mut tmp1, 9)
|
||||||
|
table.select_into(mut tmp2, 11)
|
||||||
|
table.select_into(mut tmp3, 7)
|
||||||
|
table.select_into(mut tmp4, 13)
|
||||||
|
// Expect T1 + T2 = T3 + T4
|
||||||
|
|
||||||
|
mut acc_p1 := ProjectiveP1{}
|
||||||
|
mut lhs := new_identity_point()
|
||||||
|
mut rhs := new_identity_point()
|
||||||
|
|
||||||
|
acc_p1.add(lhs, tmp1)
|
||||||
|
lhs.from_p1(acc_p1)
|
||||||
|
acc_p1.add(lhs, tmp2)
|
||||||
|
lhs.from_p1(acc_p1)
|
||||||
|
|
||||||
|
acc_p1.add(rhs, tmp3)
|
||||||
|
rhs.from_p1(acc_p1)
|
||||||
|
acc_p1.add(rhs, tmp4)
|
||||||
|
rhs.from_p1(acc_p1)
|
||||||
|
|
||||||
|
assert lhs.equal(rhs) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_naf_lookup_table8() {
|
||||||
|
mut table := NafLookupTable8{}
|
||||||
|
b := new_generator_point()
|
||||||
|
table.from_p3(b)
|
||||||
|
|
||||||
|
mut tmp1 := AffineCached{}
|
||||||
|
mut tmp2 := AffineCached{}
|
||||||
|
mut tmp3 := AffineCached{}
|
||||||
|
mut tmp4 := AffineCached{}
|
||||||
|
|
||||||
|
table.select_into(mut tmp1, 49)
|
||||||
|
table.select_into(mut tmp2, 11)
|
||||||
|
table.select_into(mut tmp3, 35)
|
||||||
|
table.select_into(mut tmp4, 25)
|
||||||
|
// Expect T1 + T2 = T3 + T4
|
||||||
|
|
||||||
|
mut acc_p1 := ProjectiveP1{}
|
||||||
|
mut lhs := new_identity_point()
|
||||||
|
mut rhs := new_identity_point()
|
||||||
|
|
||||||
|
acc_p1.add_affine(lhs, tmp1)
|
||||||
|
lhs.from_p1(acc_p1)
|
||||||
|
acc_p1.add_affine(lhs, tmp2)
|
||||||
|
lhs.from_p1(acc_p1)
|
||||||
|
|
||||||
|
acc_p1.add_affine(rhs, tmp3)
|
||||||
|
rhs.from_p1(acc_p1)
|
||||||
|
acc_p1.add_affine(rhs, tmp4)
|
||||||
|
rhs.from_p1(acc_p1)
|
||||||
|
|
||||||
|
assert lhs.equal(rhs) == 1
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a:d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a::e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b:
|
||||||
|
4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:72:92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c0072:
|
||||||
|
c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:af82:6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40aaf82:
|
||||||
|
0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:cbc77b:d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00ccbc77b:
|
||||||
|
6df9340c138cc188b5fe4464ebaa3f7fc206a2d55c3434707e74c9fc04e20ebbc0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7:c0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7:5f4c8989:124f6fc6b0d100842769e71bd530664d888df8507df6c56dedfdb509aeb93416e26b918d38aa06305df3095697c18b2aa832eaa52edc0ae49fbae5a85e150c075f4c8989:
|
||||||
|
b780381a65edf8b78f6945e8dbec7941ac049fd4c61040cf0c324357975a293ce253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01:e253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01:18b6bec097:b2fc46ad47af464478c199e1f8be169f1be6327c7f9a0a6689371ca94caf04064a01b22aff1520abd58951341603faed768cf78ce97ae7b038abfe456aa17c0918b6bec097:
|
||||||
|
78ae9effe6f245e924a7be63041146ebc670dbd3060cba67fbc6216febc44546fbcfbfa40505d7f2be444a33d185cc54e16d615260e1640b2b5087b83ee3643d:fbcfbfa40505d7f2be444a33d185cc54e16d615260e1640b2b5087b83ee3643d:89010d855972:6ed629fc1d9ce9e1468755ff636d5a3f40a5d9c91afd93b79d241830f7e5fa29854b8f20cc6eecbb248dbd8d16d14e99752194e4904d09c74d639518839d230089010d855972:
|
||||||
|
691865bfc82a1e4b574eecde4c7519093faf0cf867380234e3664645c61c5f7998a5e3a36e67aaba89888bf093de1ad963e774013b3902bfab356d8b90178a63:98a5e3a36e67aaba89888bf093de1ad963e774013b3902bfab356d8b90178a63:b4a8f381e70e7a:6e0af2fe55ae377a6b7a7278edfb419bd321e06d0df5e27037db8812e7e3529810fa5552f6c0020985ca17a0e02e036d7b222a24f99b77b75fdd16cb05568107b4a8f381e70e7a:
|
||||||
|
3b26516fb3dc88eb181b9ed73f0bcd52bcd6b4c788e4bcaf46057fd078bee073f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863:f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863:4284abc51bb67235:d6addec5afb0528ac17bb178d3e7f2887f9adbb1ad16e110545ef3bc57f9de2314a5c8388f723b8907be0f3ac90c6259bbe885ecc17645df3db7d488f805fa084284abc51bb67235:
|
||||||
|
edc6f5fbdd1cee4d101c063530a30490b221be68c036f5b07d0f953b745df192c1a49c66e617f9ef5ec66bc4c6564ca33de2a5fb5e1464062e6d6c6219155efd:c1a49c66e617f9ef5ec66bc4c6564ca33de2a5fb5e1464062e6d6c6219155efd:672bf8965d04bc5146:2c76a04af2391c147082e33faacdbe56642a1e134bd388620b852b901a6bc16ff6c9cc9404c41dea12ed281da067a1513866f9d964f8bdd24953856c50042901672bf8965d04bc5146:
|
||||||
|
4e7d21fb3b1897571a445833be0f9fd41cd62be3aa04040f8934e1fcbdcacd4531b2524b8348f7ab1dfafa675cc538e9a84e3fe5819e27c12ad8bbc1a36e4dff:31b2524b8348f7ab1dfafa675cc538e9a84e3fe5819e27c12ad8bbc1a36e4dff:33d7a786aded8c1bf691:28e4598c415ae9de01f03f9f3fab4e919e8bf537dd2b0cdf6e79b9e6559c9409d9151a4c40f083193937627c369488259e99da5a9f0a87497fa6696a5dd6ce0833d7a786aded8c1bf691:
|
||||||
|
a980f892db13c99a3e8971e965b2ff3d41eafd54093bc9f34d1fd22d84115bb644b57ee30cdb55829d0a5d4f046baef078f1e97a7f21b62d75f8e96ea139c35f:44b57ee30cdb55829d0a5d4f046baef078f1e97a7f21b62d75f8e96ea139c35f:3486f68848a65a0eb5507d:77d389e599630d934076329583cd4105a649a9292abc44cd28c40000c8e2f5ac7660a81c85b72af8452d7d25c070861dae91601c7803d656531650dd4e5c41003486f68848a65a0eb5507d:
|
||||||
|
5b5a619f8ce1c66d7ce26e5a2ae7b0c04febcd346d286c929e19d0d5973bfef96fe83693d011d111131c4f3fbaaa40a9d3d76b30012ff73bb0e39ec27ab18257:6fe83693d011d111131c4f3fbaaa40a9d3d76b30012ff73bb0e39ec27ab18257:5a8d9d0a22357e6655f9c785:0f9ad9793033a2fa06614b277d37381e6d94f65ac2a5a94558d09ed6ce922258c1a567952e863ac94297aec3c0d0c8ddf71084e504860bb6ba27449b55adc40e5a8d9d0a22357e6655f9c785:
|
||||||
|
940c89fe40a81dafbdb2416d14ae469119869744410c3303bfaa0241dac57800a2eb8c0501e30bae0cf842d2bde8dec7386f6b7fc3981b8c57c9792bb94cf2dd:a2eb8c0501e30bae0cf842d2bde8dec7386f6b7fc3981b8c57c9792bb94cf2dd:b87d3813e03f58cf19fd0b6395:d8bb64aad8c9955a115a793addd24f7f2b077648714f49c4694ec995b330d09d640df310f447fd7b6cb5c14f9fe9f490bcf8cfadbfd2169c8ac20d3b8af49a0cb87d3813e03f58cf19fd0b6395:
|
||||||
|
9acad959d216212d789a119252ebfe0c96512a23c73bd9f3b202292d6916a738cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291:cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291:55c7fa434f5ed8cdec2b7aeac173:6ee3fe81e23c60eb2312b2006b3b25e6838e02106623f844c44edb8dafd66ab0671087fd195df5b8f58a1d6e52af42908053d55c7321010092748795ef94cf0655c7fa434f5ed8cdec2b7aeac173:
|
||||||
|
d5aeee41eeb0e9d1bf8337f939587ebe296161e6bf5209f591ec939e1440c300fd2a565723163e29f53c9de3d5e8fbe36a7ab66e1439ec4eae9c0a604af291a5:fd2a565723163e29f53c9de3d5e8fbe36a7ab66e1439ec4eae9c0a604af291a5:0a688e79be24f866286d4646b5d81c:f68d04847e5b249737899c014d31c805c5007a62c0a10d50bb1538c5f35503951fbc1e08682f2cc0c92efe8f4985dec61dcbd54d4b94a22547d24451271c8b000a688e79be24f866286d4646b5d81c:
|
||||||
|
0a47d10452ae2febec518a1c7c362890c3fc1a49d34b03b6467d35c904a8362d34e5a8508c4743746962c066e4badea2201b8ab484de5c4f94476ccd2143955b:34e5a8508c4743746962c066e4badea2201b8ab484de5c4f94476ccd2143955b:c942fa7ac6b23ab7ff612fdc8e68ef39:2a3d27dc40d0a8127949a3b7f908b3688f63b7f14f651aacd715940bdbe27a0809aac142f47ab0e1e44fa490ba87ce5392f33a891539caf1ef4c367cae54500cc942fa7ac6b23ab7ff612fdc8e68ef39:
|
||||||
|
f8148f7506b775ef46fdc8e8c756516812d47d6cfbfa318c27c9a22641e56f170445e456dacc7d5b0bbed23c8200cdb74bdcb03e4c7b73f0a2b9b46eac5d4372:0445e456dacc7d5b0bbed23c8200cdb74bdcb03e4c7b73f0a2b9b46eac5d4372:7368724a5b0efb57d28d97622dbde725af:3653ccb21219202b8436fb41a32ba2618c4a133431e6e63463ceb3b6106c4d56e1d2ba165ba76eaad3dc39bffb130f1de3d8e6427db5b71938db4e272bc3e20b7368724a5b0efb57d28d97622dbde725af:
|
||||||
|
77f88691c4eff23ebb7364947092951a5ff3f10785b417e918823a552dab7c7574d29127f199d86a8676aec33b4ce3f225ccb191f52c191ccd1e8cca65213a6b:74d29127f199d86a8676aec33b4ce3f225ccb191f52c191ccd1e8cca65213a6b:bd8e05033f3a8bcdcbf4beceb70901c82e31:fbe929d743a03c17910575492f3092ee2a2bf14a60a3fcacec74a58c7334510fc262db582791322d6c8c41f1700adb80027ecabc14270b703444ae3ee7623e0abd8e05033f3a8bcdcbf4beceb70901c82e31:
|
||||||
|
ab6f7aee6a0837b334ba5eb1b2ad7fcecfab7e323cab187fe2e0a95d80eff1325b96dca497875bf9664c5e75facf3f9bc54bae913d66ca15ee85f1491ca24d2c:5b96dca497875bf9664c5e75facf3f9bc54bae913d66ca15ee85f1491ca24d2c:8171456f8b907189b1d779e26bc5afbb08c67a:73bca64e9dd0db88138eedfafcea8f5436cfb74bfb0e7733cf349baa0c49775c56d5934e1d38e36f39b7c5beb0a836510c45126f8ec4b6810519905b0ca07c098171456f8b907189b1d779e26bc5afbb08c67a:
|
||||||
|
8d135de7c8411bbdbd1b31e5dc678f2ac7109e792b60f38cd24936e8a898c32d1ca281938529896535a7714e3584085b86ef9fec723f42819fc8dd5d8c00817f:1ca281938529896535a7714e3584085b86ef9fec723f42819fc8dd5d8c00817f:8ba6a4c9a15a244a9c26bb2a59b1026f21348b49:a1adc2bc6a2d980662677e7fdff6424de7dba50f5795ca90fdf3e96e256f3285cac71d3360482e993d0294ba4ec7440c61affdf35fe83e6e04263937db93f1058ba6a4c9a15a244a9c26bb2a59b1026f21348b49:
|
||||||
|
0e765d720e705f9366c1ab8c3fa84c9a44370c06969f803296884b2846a652a47fae45dd0a05971026d410bc497af5be7d0827a82a145c203f625dfcb8b03ba8:7fae45dd0a05971026d410bc497af5be7d0827a82a145c203f625dfcb8b03ba8:1d566a6232bbaab3e6d8804bb518a498ed0f904986:bb61cf84de61862207c6a455258bc4db4e15eea0317ff88718b882a06b5cf6ec6fd20c5a269e5d5c805bafbcc579e2590af414c7c227273c102a10070cdfe80f1d566a6232bbaab3e6d8804bb518a498ed0f904986:
|
||||||
|
db36e326d676c2d19cc8fe0c14b709202ecfc761d27089eb6ea4b1bb021ecfa748359b850d23f0715d94bb8bb75e7e14322eaf14f06f28a805403fbda002fc85:48359b850d23f0715d94bb8bb75e7e14322eaf14f06f28a805403fbda002fc85:1b0afb0ac4ba9ab7b7172cddc9eb42bba1a64bce47d4:b6dcd09989dfbac54322a3ce87876e1d62134da998c79d24b50bd7a6a797d86a0e14dc9d7491d6c14a673c652cfbec9f962a38c945da3b2f0879d0b68a9213001b0afb0ac4ba9ab7b7172cddc9eb42bba1a64bce47d4:
|
||||||
|
c89955e0f7741d905df0730b3dc2b0ce1a13134e44fef3d40d60c020ef19df77fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c:fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c:507c94c8820d2a5793cbf3442b3d71936f35fe3afef316:7ef66e5e86f2360848e0014e94880ae2920ad8a3185a46b35d1e07dea8fa8ae4f6b843ba174d99fa7986654a0891c12a794455669375bf92af4cc2770b579e0c507c94c8820d2a5793cbf3442b3d71936f35fe3afef316:
|
||||||
|
4e62627fc221142478aee7f00781f817f662e3b75db29bb14ab47cf8e84104d6b1d39801892027d58a8c64335163195893bfc1b61dbeca3260497e1f30371107:b1d39801892027d58a8c64335163195893bfc1b61dbeca3260497e1f30371107:d3d615a8472d9962bb70c5b5466a3d983a4811046e2a0ef5:836afa764d9c48aa4770a4388b654e97b3c16f082967febca27f2fc47ddfd9244b03cfc729698acf5109704346b60b230f255430089ddc56912399d1122de70ad3d615a8472d9962bb70c5b5466a3d983a4811046e2a0ef5:
|
||||||
|
6b83d7da8908c3e7205b39864b56e5f3e17196a3fc9c2f5805aad0f5554c142dd0c846f97fe28585c0ee159015d64c56311c886eddcc185d296dbb165d2625d6:d0c846f97fe28585c0ee159015d64c56311c886eddcc185d296dbb165d2625d6:6ada80b6fa84f7034920789e8536b82d5e4678059aed27f71c:16e462a29a6dd498685a3718b3eed00cc1598601ee47820486032d6b9acc9bf89f57684e08d8c0f05589cda2882a05dc4c63f9d0431d6552710812433003bc086ada80b6fa84f7034920789e8536b82d5e4678059aed27f71c:
|
||||||
|
19a91fe23a4e9e33ecc474878f57c64cf154b394203487a7035e1ad9cd697b0d2bf32ba142ba4622d8f3e29ecd85eea07b9c47be9d64412c9b510b27dd218b23:2bf32ba142ba4622d8f3e29ecd85eea07b9c47be9d64412c9b510b27dd218b23:82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c:881f5b8c5a030df0f75b6634b070dd27bd1ee3c08738ae349338b3ee6469bbf9760b13578a237d5182535ede121283027a90b5f865d63a6537dca07b44049a0f82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c:
|
||||||
|
1d5b8cb6215c18141666baeefcf5d69dad5bea9a3493dddaa357a4397a13d4de94d23d977c33e49e5e4992c68f25ec99a27c41ce6b91f2bfa0cd8292fe962835:94d23d977c33e49e5e4992c68f25ec99a27c41ce6b91f2bfa0cd8292fe962835:a9a8cbb0ad585124e522abbfb40533bdd6f49347b55b18e8558cb0:3acd39bec8c3cd2b44299722b5850a0400c1443590fd4861d59aae7496acb3df73fc3fdf7969ae5f50ba47dddc435246e5fd376f6b891cd4c2caf5d614b6170ca9a8cbb0ad585124e522abbfb40533bdd6f49347b55b18e8558cb0:
|
||||||
|
6a91b3227c472299089bdce9356e726a40efd840f11002708b7ee55b64105ac29d084aa8b97a6b9bafa496dbc6f76f3306a116c9d917e681520a0f914369427e:9d084aa8b97a6b9bafa496dbc6f76f3306a116c9d917e681520a0f914369427e:5cb6f9aa59b80eca14f6a68fb40cf07b794e75171fba96262c1c6adc:f5875423781b66216cb5e8998de5d9ffc29d1d67107054ace3374503a9c3ef811577f269de81296744bd706f1ac478caf09b54cdf871b3f802bd57f9a6cb91015cb6f9aa59b80eca14f6a68fb40cf07b794e75171fba96262c1c6adc:
|
||||||
|
93eaa854d791f05372ce72b94fc6503b2ff8ae6819e6a21afe825e27ada9e4fb16cee8a3f2631834c88b670897ff0b08ce90cc147b4593b3f1f403727f7e7ad5:16cee8a3f2631834c88b670897ff0b08ce90cc147b4593b3f1f403727f7e7ad5:32fe27994124202153b5c70d3813fdee9c2aa6e7dc743d4d535f1840a5:d834197c1a3080614e0a5fa0aaaa808824f21c38d692e6ffbd200f7dfb3c8f44402a7382180b98ad0afc8eec1a02acecf3cb7fde627b9f18111f260ab1db9a0732fe27994124202153b5c70d3813fdee9c2aa6e7dc743d4d535f1840a5:
|
||||||
|
941cac69fb7b1815c57bb987c4d6c2ad2c35d5f9a3182a79d4ba13eab253a8ad23be323c562dfd71ce65f5bba56a74a3a6dfc36b573d2f94f635c7f9b4fd5a5b:23be323c562dfd71ce65f5bba56a74a3a6dfc36b573d2f94f635c7f9b4fd5a5b:bb3172795710fe00054d3b5dfef8a11623582da68bf8e46d72d27cece2aa:0f8fad1e6bde771b4f5420eac75c378bae6db5ac6650cd2bc210c1823b432b48e016b10595458ffab92f7a8989b293ceb8dfed6c243a2038fc06652aaaf16f02bb3172795710fe00054d3b5dfef8a11623582da68bf8e46d72d27cece2aa:
|
||||||
|
1acdbb793b0384934627470d795c3d1dd4d79cea59ef983f295b9b59179cbb283f60c7541afa76c019cf5aa82dcdb088ed9e4ed9780514aefb379dabc844f31a:3f60c7541afa76c019cf5aa82dcdb088ed9e4ed9780514aefb379dabc844f31a:7cf34f75c3dac9a804d0fcd09eba9b29c9484e8a018fa9e073042df88e3c56:be71ef4806cb041d885effd9e6b0fbb73d65d7cdec47a89c8a994892f4e55a568c4cc78d61f901e80dbb628b86a23ccd594e712b57fa94c2d67ec266348785077cf34f75c3dac9a804d0fcd09eba9b29c9484e8a018fa9e073042df88e3c56:
|
||||||
|
8ed7a797b9cea8a8370d419136bcdf683b759d2e3c6947f17e13e2485aa9d420b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9:b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9:a750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3:04266c033b91c1322ceb3446c901ffcf3cc40c4034e887c9597ca1893ba7330becbbd8b48142ef35c012c6ba51a66df9308cb6268ad6b1e4b03e70102495790ba750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3:
|
||||||
|
f2ab396fe8906e3e5633e99cabcd5b09df0859b516230b1e0450b580b65f616c8ea074245159a116aa7122a25ec16b891d625a68f33660423908f6bdc44f8c1b:8ea074245159a116aa7122a25ec16b891d625a68f33660423908f6bdc44f8c1b:5a44e34b746c5fd1898d552ab354d28fb4713856d7697dd63eb9bd6b99c280e187:a06a23d982d81ab883aae230adbc368a6a9977f003cebb00d4c2e4018490191a84d3a282fdbfb2fc88046e62de43e15fb575336b3c8b77d19ce6a009ce51f50c5a44e34b746c5fd1898d552ab354d28fb4713856d7697dd63eb9bd6b99c280e187:
|
||||||
|
550a41c013f79bab8f06e43ad1836d51312736a9713806fafe6645219eaa1f9daf6b7145474dc9954b9af93a9cdb34449d5b7c651c824d24e230b90033ce59c0:af6b7145474dc9954b9af93a9cdb34449d5b7c651c824d24e230b90033ce59c0:8bc4185e50e57d5f87f47515fe2b1837d585f0aae9e1ca383b3ec908884bb900ff27:16dc1e2b9fa909eefdc277ba16ebe207b8da5e91143cde78c5047a89f681c33c4e4e3428d5c928095903a811ec002d52a39ed7f8b3fe1927200c6dd0b9ab3e048bc4185e50e57d5f87f47515fe2b1837d585f0aae9e1ca383b3ec908884bb900ff27:
|
||||||
|
19ac3e272438c72ddf7b881964867cb3b31ff4c793bb7ea154613c1db068cb7ef85b80e050a1b9620db138bfc9e100327e25c257c59217b601f1f6ac9a413d3f:f85b80e050a1b9620db138bfc9e100327e25c257c59217b601f1f6ac9a413d3f:95872d5f789f95484e30cbb0e114028953b16f5c6a8d9f65c003a83543beaa46b38645:ea855d781cbea4682e350173cb89e8619ccfddb97cdce16f9a2f6f6892f46dbe68e04b12b8d88689a7a31670cdff409af98a93b49a34537b6aa009d2eb8b470195872d5f789f95484e30cbb0e114028953b16f5c6a8d9f65c003a83543beaa46b38645:
|
||||||
|
ca267de96c93c238fafb1279812059ab93ac03059657fd994f8fa5a09239c821017370c879090a81c7f272c2fc80e3aac2bc603fcb379afc98691160ab745b26:017370c879090a81c7f272c2fc80e3aac2bc603fcb379afc98691160ab745b26:e05f71e4e49a72ec550c44a3b85aca8f20ff26c3ee94a80f1b431c7d154ec9603ee02531:ac957f82335aa7141e96b59d63e3ccee95c3a2c47d026540c2af42dc9533d5fd81827d1679ad187aeaf37834915e75b147a9286806c8017516ba43dd051a5e0ce05f71e4e49a72ec550c44a3b85aca8f20ff26c3ee94a80f1b431c7d154ec9603ee02531:
|
||||||
|
3dff5e899475e7e91dd261322fab09980c52970de1da6e2e201660cc4fce7032f30162bac98447c4042fac05da448034629be2c6a58d30dfd578ba9fb5e3930b:f30162bac98447c4042fac05da448034629be2c6a58d30dfd578ba9fb5e3930b:938f0e77621bf3ea52c7c4911c5157c2d8a2a858093ef16aa9b107e69d98037ba139a3c382:5efe7a92ff9623089b3e3b78f352115366e26ba3fb1a416209bc029e9cadccd9f4affa333555a8f3a35a9d0f7c34b292cae77ec96fa3adfcaadee2d9ced8f805938f0e77621bf3ea52c7c4911c5157c2d8a2a858093ef16aa9b107e69d98037ba139a3c382:
|
||||||
|
9a6b847864e70cfe8ba6ab22fa0ca308c0cc8bec7141fbcaa3b81f5d1e1cfcfc34ad0fbdb2566507a81c2b1f8aa8f53dccaa64cc87ada91b903e900d07eee930:34ad0fbdb2566507a81c2b1f8aa8f53dccaa64cc87ada91b903e900d07eee930:838367471183c71f7e717724f89d401c3ad9863fd9cc7aa3cf33d3c529860cb581f3093d87da:2ab255169c489c54c732232e37c87349d486b1eba20509dbabe7fed329ef08fd75ba1cd145e67b2ea26cb5cc51cab343eeb085fe1fd7b0ec4c6afcd9b979f905838367471183c71f7e717724f89d401c3ad9863fd9cc7aa3cf33d3c529860cb581f3093d87da:
|
||||||
|
575be07afca5d063c238cd9b8028772cc49cda34471432a2e166e096e2219efc94e5eb4d5024f49d7ebf79817c8de11497dc2b55622a51ae123ffc749dbb16e0:94e5eb4d5024f49d7ebf79817c8de11497dc2b55622a51ae123ffc749dbb16e0:33e5918b66d33d55fe717ca34383eae78f0af82889caf6696e1ac9d95d1ffb32cba755f9e3503e:58271d44236f3b98c58fd7ae0d2f49ef2b6e3affdb225aa3ba555f0e11cc53c23ad19baf24346590d05d7d5390582082cf94d39cad6530ab93d13efb3927950633e5918b66d33d55fe717ca34383eae78f0af82889caf6696e1ac9d95d1ffb32cba755f9e3503e:
|
||||||
|
15ffb45514d43444d61fcb105e30e135fd268523dda20b82758b1794231104411772c5abc2d23fd2f9d1c3257be7bc3c1cd79cee40844b749b3a7743d2f964b8:1772c5abc2d23fd2f9d1c3257be7bc3c1cd79cee40844b749b3a7743d2f964b8:da9c5559d0ea51d255b6bd9d7638b876472f942b330fc0e2b30aea68d77368fce4948272991d257e:6828cd7624e793b8a4ceb96d3c2a975bf773e5ff6645f353614058621e58835289e7f31f42dfe6af6d736f2644511e320c0fa698582a79778d18730ed3e8cb08da9c5559d0ea51d255b6bd9d7638b876472f942b330fc0e2b30aea68d77368fce4948272991d257e:
|
||||||
|
fe0568642943b2e1afbfd1f10fe8df87a4236bea40dce742072cb21886eec1fa299ebd1f13177dbdb66a912bbf712038fdf73b06c3ac020c7b19126755d47f61:299ebd1f13177dbdb66a912bbf712038fdf73b06c3ac020c7b19126755d47f61:c59d0862ec1c9746abcc3cf83c9eeba2c7082a036a8cb57ce487e763492796d47e6e063a0c1feccc2d:d59e6dfcc6d7e3e2c58dec81e985d245e681acf6594a23c59214f7bed8015d813c7682b60b3583440311e72a8665ba2c96dec23ce826e160127e18132b030404c59d0862ec1c9746abcc3cf83c9eeba2c7082a036a8cb57ce487e763492796d47e6e063a0c1feccc2d:
|
||||||
|
5ecb16c2df27c8cf58e436a9d3affbd58e9538a92659a0f97c4c4f994635a8cada768b20c437dd3aa5f84bb6a077ffa34ab68501c5352b5cc3fdce7fe6c2398d:da768b20c437dd3aa5f84bb6a077ffa34ab68501c5352b5cc3fdce7fe6c2398d:56f1329d9a6be25a6159c72f12688dc8314e85dd9e7e4dc05bbecb7729e023c86f8e0937353f27c7ede9:1c723a20c6772426a670e4d5c4a97c6ebe9147f71bb0a415631e44406e290322e4ca977d348fe7856a8edc235d0fe95f7ed91aefddf28a77e2c7dbfd8f552f0a56f1329d9a6be25a6159c72f12688dc8314e85dd9e7e4dc05bbecb7729e023c86f8e0937353f27c7ede9:
|
||||||
|
d599d637b3c30a82a9984e2f758497d144de6f06b9fba04dd40fd949039d7c846791d8ce50a44689fc178727c5c3a1c959fbeed74ef7d8e7bd3c1ab4da31c51f:6791d8ce50a44689fc178727c5c3a1c959fbeed74ef7d8e7bd3c1ab4da31c51f:a7c04e8ba75d0a03d8b166ad7a1d77e1b91c7aaf7befdd99311fc3c54a684ddd971d5b3211c3eeaff1e54e:ebf10d9ac7c96108140e7def6fe9533d727646ff5b3af273c1df95762a66f32b65a09634d013f54b5dd6011f91bc336ca8b355ce33f8cfbec2535a4c427f8205a7c04e8ba75d0a03d8b166ad7a1d77e1b91c7aaf7befdd99311fc3c54a684ddd971d5b3211c3eeaff1e54e:
|
||||||
|
30ab8232fa7018f0ce6c39bd8f782fe2e159758bb0f2f4386c7f28cfd2c85898ecfb6a2bd42f31b61250ba5de7e46b4719afdfbc660db71a7bd1df7b0a3abe37:ecfb6a2bd42f31b61250ba5de7e46b4719afdfbc660db71a7bd1df7b0a3abe37:63b80b7956acbecf0c35e9ab06b914b0c7014fe1a4bbc0217240c1a33095d707953ed77b15d211adaf9b97dc:9af885344cc7239498f712df80bc01b80638291ed4a1d28baa5545017a72e2f65649ccf9603da6eb5bfab9f5543a6ca4a7af3866153c76bf66bf95def615b00c63b80b7956acbecf0c35e9ab06b914b0c7014fe1a4bbc0217240c1a33095d707953ed77b15d211adaf9b97dc:
|
||||||
|
0ddcdc872c7b748d40efe96c2881ae189d87f56148ed8af3ebbbc80324e38bdd588ddadcbcedf40df0e9697d8bb277c7bb1498fa1d26ce0a835a760b92ca7c85:588ddadcbcedf40df0e9697d8bb277c7bb1498fa1d26ce0a835a760b92ca7c85:65641cd402add8bf3d1d67dbeb6d41debfbef67e4317c35b0a6d5bbbae0e034de7d670ba1413d056f2d6f1de12:c179c09456e235fe24105afa6e8ec04637f8f943817cd098ba95387f9653b2add181a31447d92d1a1ddf1ceb0db62118de9dffb7dcd2424057cbdff5d41d040365641cd402add8bf3d1d67dbeb6d41debfbef67e4317c35b0a6d5bbbae0e034de7d670ba1413d056f2d6f1de12:
|
||||||
|
89f0d68299ba0a5a83f248ae0c169f8e3849a9b47bd4549884305c9912b46603aba3e795aab2012acceadd7b3bd9daeeed6ff5258bdcd7c93699c2a3836e3832:aba3e795aab2012acceadd7b3bd9daeeed6ff5258bdcd7c93699c2a3836e3832:4f1846dd7ad50e545d4cfbffbb1dc2ff145dc123754d08af4e44ecc0bc8c91411388bc7653e2d893d1eac2107d05:2c691fa8d487ce20d5d2fa41559116e0bbf4397cf5240e152556183541d66cf753582401a4388d390339dbef4d384743caa346f55f8daba68ba7b9131a8a6e0b4f1846dd7ad50e545d4cfbffbb1dc2ff145dc123754d08af4e44ecc0bc8c91411388bc7653e2d893d1eac2107d05:
|
||||||
|
0a3c1844e2db070fb24e3c95cb1cc6714ef84e2ccd2b9dd2f1460ebf7ecf13b172e409937e0610eb5c20b326dc6ea1bbbc0406701c5cd67d1fbde09192b07c01:72e409937e0610eb5c20b326dc6ea1bbbc0406701c5cd67d1fbde09192b07c01:4c8274d0ed1f74e2c86c08d955bde55b2d54327e82062a1f71f70d536fdc8722cdead7d22aaead2bfaa1ad00b82957:87f7fdf46095201e877a588fe3e5aaf476bd63138d8a878b89d6ac60631b3458b9d41a3c61a588e1db8d29a5968981b018776c588780922f5aa732ba6379dd054c8274d0ed1f74e2c86c08d955bde55b2d54327e82062a1f71f70d536fdc8722cdead7d22aaead2bfaa1ad00b82957:
|
||||||
|
c8d7a8818b98dfdb20839c871cb5c48e9e9470ca3ad35ba2613a5d3199c8ab2390d2efbba4d43e6b2b992ca16083dbcfa2b322383907b0ee75f3e95845d3c47f:90d2efbba4d43e6b2b992ca16083dbcfa2b322383907b0ee75f3e95845d3c47f:783e33c3acbdbb36e819f544a7781d83fc283d3309f5d3d12c8dcd6b0b3d0e89e38cfd3b4d0885661ca547fb9764abff:fa2e994421aef1d5856674813d05cbd2cf84ef5eb424af6ecd0dc6fdbdc2fe605fe985883312ecf34f59bfb2f1c9149e5b9cc9ecda05b2731130f3ed28ddae0b783e33c3acbdbb36e819f544a7781d83fc283d3309f5d3d12c8dcd6b0b3d0e89e38cfd3b4d0885661ca547fb9764abff:
|
||||||
|
b482703612d0c586f76cfcb21cfd2103c957251504a8c0ac4c86c9c6f3e429fffd711dc7dd3b1dfb9df9704be3e6b26f587fe7dd7ba456a91ba43fe51aec09ad:fd711dc7dd3b1dfb9df9704be3e6b26f587fe7dd7ba456a91ba43fe51aec09ad:29d77acfd99c7a0070a88feb6247a2bce9984fe3e6fbf19d4045042a21ab26cbd771e184a9a75f316b648c6920db92b87b:58832bdeb26feafc31b46277cf3fb5d7a17dfb7ccd9b1f58ecbe6feb979666828f239ba4d75219260ecac0acf40f0e5e2590f4caa16bbbcd8a155d347967a60729d77acfd99c7a0070a88feb6247a2bce9984fe3e6fbf19d4045042a21ab26cbd771e184a9a75f316b648c6920db92b87b:
|
||||||
|
84e50dd9a0f197e3893c38dbd91fafc344c1776d3a400e2f0f0ee7aa829eb8a22c50f870ee48b36b0ac2f8a5f336fb090b113050dbcc25e078200a6e16153eea:2c50f870ee48b36b0ac2f8a5f336fb090b113050dbcc25e078200a6e16153eea:f3992cde6493e671f1e129ddca8038b0abdb77bb9035f9f8be54bd5d68c1aeff724ff47d29344391dc536166b8671cbbf123:69e6a4491a63837316e86a5f4ba7cd0d731ecc58f1d0a264c67c89befdd8d3829d8de13b33cc0bf513931715c7809657e2bfb960e5c764c971d733746093e500f3992cde6493e671f1e129ddca8038b0abdb77bb9035f9f8be54bd5d68c1aeff724ff47d29344391dc536166b8671cbbf123:
|
||||||
|
b322d46577a2a991a4d1698287832a39c487ef776b4bff037a05c7f1812bdeeceb2bcadfd3eec2986baff32b98e7c4dbf03ff95d8ad5ff9aa9506e5472ff845f:eb2bcadfd3eec2986baff32b98e7c4dbf03ff95d8ad5ff9aa9506e5472ff845f:19f1bf5dcf1750c611f1c4a2865200504d82298edd72671f62a7b1471ac3d4a30f7de9e5da4108c52a4ce70a3e114a52a3b3c5:c7b55137317ca21e33489ff6a9bfab97c855dc6f85684a70a9125a261b56d5e6f149c5774d734f2d8debfc77b721896a8267c23768e9badb910eef83ec25880219f1bf5dcf1750c611f1c4a2865200504d82298edd72671f62a7b1471ac3d4a30f7de9e5da4108c52a4ce70a3e114a52a3b3c5:
|
||||||
|
960cab5034b9838d098d2dcbf4364bec16d388f6376d73a6273b70f82bbc98c05e3c19f2415acf729f829a4ebd5c40e1a6bc9fbca95703a9376087ed0937e51a:5e3c19f2415acf729f829a4ebd5c40e1a6bc9fbca95703a9376087ed0937e51a:f8b21962447b0a8f2e4279de411bea128e0be44b6915e6cda88341a68a0d818357db938eac73e0af6d31206b3948f8c48a447308:27d4c3a1811ef9d4360b3bdd133c2ccc30d02c2f248215776cb07ee4177f9b13fc42dd70a6c2fed8f225c7663c7f182e7ee8eccff20dc7b0e1d5834ec5b1ea01f8b21962447b0a8f2e4279de411bea128e0be44b6915e6cda88341a68a0d818357db938eac73e0af6d31206b3948f8c48a447308:
|
||||||
|
eb77b2638f23eebc82efe45ee9e5a0326637401e663ed029699b21e6443fb48e9ef27608961ac711de71a6e2d4d4663ea3ecd42fb7e4e8627c39622df4af0bbc:9ef27608961ac711de71a6e2d4d4663ea3ecd42fb7e4e8627c39622df4af0bbc:99e3d00934003ebafc3e9fdb687b0f5ff9d5782a4b1f56b9700046c077915602c3134e22fc90ed7e690fddd4433e2034dcb2dc99ab:18dc56d7bd9acd4f4daa78540b4ac8ff7aa9815f45a0bba370731a14eaabe96df8b5f37dbf8eae4cb15a64b244651e59d6a3d6761d9e3c50f2d0cbb09c05ec0699e3d00934003ebafc3e9fdb687b0f5ff9d5782a4b1f56b9700046c077915602c3134e22fc90ed7e690fddd4433e2034dcb2dc99ab:
|
||||||
|
b625aa89d3f7308715427b6c39bbac58effd3a0fb7316f7a22b99ee5922f2dc965a99c3e16fea894ec33c6b20d9105e2a04e2764a4769d9bbd4d8bacfeab4a2e:65a99c3e16fea894ec33c6b20d9105e2a04e2764a4769d9bbd4d8bacfeab4a2e:e07241dbd3adbe610bbe4d005dd46732a4c25086ecb8ec29cd7bca116e1bf9f53bfbf3e11fa49018d39ff1154a06668ef7df5c678e6a:01bb901d83b8b682d3614af46a807ba2691358feb775325d3423f549ff0aa5757e4e1a74e9c70f9721d8f354b319d4f4a1d91445c870fd0ffb94fed64664730de07241dbd3adbe610bbe4d005dd46732a4c25086ecb8ec29cd7bca116e1bf9f53bfbf3e11fa49018d39ff1154a06668ef7df5c678e6a:
|
||||||
|
b1c9f8bd03fe82e78f5c0fb06450f27dacdf716434db268275df3e1dc177af427fc88b1f7b3f11c629be671c21621f5c10672fafc8492da885742059ee6774cf:7fc88b1f7b3f11c629be671c21621f5c10672fafc8492da885742059ee6774cf:331da7a9c1f87b2ac91ee3b86d06c29163c05ed6f8d8a9725b471b7db0d6acec7f0f702487163f5eda020ca5b493f399e1c8d308c3c0c2:4b229951ef262f16978f7914bc672e7226c5f8379d2778c5a2dc0a2650869f7acfbd0bcd30fdb0619bb44fc1ae5939b87cc318133009c20395b6c7eb98107701331da7a9c1f87b2ac91ee3b86d06c29163c05ed6f8d8a9725b471b7db0d6acec7f0f702487163f5eda020ca5b493f399e1c8d308c3c0c2:
|
||||||
|
6d8cdb2e075f3a2f86137214cb236ceb89a6728bb4a200806bf3557fb78fac6957a04c7a5113cddfe49a4c124691d46c1f9cdc8f343f9dcb72a1330aeca71fda:57a04c7a5113cddfe49a4c124691d46c1f9cdc8f343f9dcb72a1330aeca71fda:7f318dbd121c08bfddfeff4f6aff4e45793251f8abf658403358238984360054f2a862c5bb83ed89025d2014a7a0cee50da3cb0e76bbb6bf:a6cbc947f9c87d1455cf1a708528c090f11ecee4855d1dbaadf47454a4de55fa4ce84b36d73a5b5f8f59298ccf21992df492ef34163d87753b7e9d32f2c3660b7f318dbd121c08bfddfeff4f6aff4e45793251f8abf658403358238984360054f2a862c5bb83ed89025d2014a7a0cee50da3cb0e76bbb6bf:
|
||||||
|
47adc6d6bf571ee9570ca0f75b604ac43e303e4ab339ca9b53cacc5be45b2ccba3f527a1c1f17dfeed92277347c9f98ab475de1755b0ab546b8a15d01b9bd0be:a3f527a1c1f17dfeed92277347c9f98ab475de1755b0ab546b8a15d01b9bd0be:ce497c5ff5a77990b7d8f8699eb1f5d8c0582f70cb7ac5c54d9d924913278bc654d37ea227590e15202217fc98dac4c0f3be2183d133315739:4e8c318343c306adbba60c92b75cb0569b9219d8a86e5d57752ed235fc109a43c2cf4e942cacf297279fbb28675347e08027722a4eb7395e00a17495d32edf0bce497c5ff5a77990b7d8f8699eb1f5d8c0582f70cb7ac5c54d9d924913278bc654d37ea227590e15202217fc98dac4c0f3be2183d133315739:
|
||||||
|
3c19b50b0fe47961719c381d0d8da9b9869d312f13e3298b97fb22f0af29cbbe0f7eda091499625e2bae8536ea35cda5483bd16a9c7e416b341d6f2c83343612:0f7eda091499625e2bae8536ea35cda5483bd16a9c7e416b341d6f2c83343612:8ddcd63043f55ec3bfc83dceae69d8f8b32f4cdb6e2aebd94b4314f8fe7287dcb62732c9052e7557fe63534338efb5b6254c5d41d2690cf5144f:efbd41f26a5d62685516f882b6ec74e0d5a71830d203c231248f26e99a9c6578ec900d68cdb8fa7216ad0d24f9ecbc9ffa655351666582f626645395a31fa7048ddcd63043f55ec3bfc83dceae69d8f8b32f4cdb6e2aebd94b4314f8fe7287dcb62732c9052e7557fe63534338efb5b6254c5d41d2690cf5144f:
|
||||||
|
34e1e9d539107eb86b393a5ccea1496d35bc7d5e9a8c5159d957e4e5852b3eb00ecb2601d5f7047428e9f909883a12420085f04ee2a88b6d95d3d7f2c932bd76:0ecb2601d5f7047428e9f909883a12420085f04ee2a88b6d95d3d7f2c932bd76:a6d4d0542cfe0d240a90507debacabce7cbbd48732353f4fad82c7bb7dbd9df8e7d9a16980a45186d8786c5ef65445bcc5b2ad5f660ffc7c8eaac0:32d22904d3e7012d6f5a441b0b4228064a5cf95b723a66b048a087ecd55920c31c204c3f2006891a85dd1932e3f1d614cfd633b5e63291c6d8166f3011431e09a6d4d0542cfe0d240a90507debacabce7cbbd48732353f4fad82c7bb7dbd9df8e7d9a16980a45186d8786c5ef65445bcc5b2ad5f660ffc7c8eaac0:
|
||||||
|
49dd473ede6aa3c866824a40ada4996c239a20d84c9365e4f0a4554f8031b9cf788de540544d3feb0c919240b390729be487e94b64ad973eb65b4669ecf23501:788de540544d3feb0c919240b390729be487e94b64ad973eb65b4669ecf23501:3a53594f3fba03029318f512b084a071ebd60baec7f55b028dc73bfc9c74e0ca496bf819dd92ab61cd8b74be3c0d6dcd128efc5ed3342cba124f726c:d2fde02791e720852507faa7c3789040d9ef86646321f313ac557f4002491542dd67d05c6990cdb0d495501fbc5d5188bfbb84dc1bf6098bee0603a47fc2690f3a53594f3fba03029318f512b084a071ebd60baec7f55b028dc73bfc9c74e0ca496bf819dd92ab61cd8b74be3c0d6dcd128efc5ed3342cba124f726c:
|
||||||
|
331c64da482b6b551373c36481a02d8136ecadbb01ab114b4470bf41607ac57152a00d96a3148b4726692d9eff89160ea9f99a5cc4389f361fed0bb16a42d521:52a00d96a3148b4726692d9eff89160ea9f99a5cc4389f361fed0bb16a42d521:20e1d05a0d5b32cc8150b8116cef39659dd5fb443ab15600f78e5b49c45326d9323f2850a63c3808859495ae273f58a51e9de9a145d774b40ba9d753d3:22c99aa946ead39ac7997562810c01c20b46bd610645bd2d56dcdcbaacc5452c74fbf4b8b1813b0e94c30d808ce5498e61d4f7ccbb4cc5f04dfc6140825a960020e1d05a0d5b32cc8150b8116cef39659dd5fb443ab15600f78e5b49c45326d9323f2850a63c3808859495ae273f58a51e9de9a145d774b40ba9d753d3:
|
||||||
|
5c0b96f2af8712122cf743c8f8dc77b6cd5570a7de13297bb3dde1886213cce20510eaf57d7301b0e1d527039bf4c6e292300a3a61b4765434f3203c100351b1:0510eaf57d7301b0e1d527039bf4c6e292300a3a61b4765434f3203c100351b1:54e0caa8e63919ca614b2bfd308ccfe50c9ea888e1ee4446d682cb5034627f97b05392c04e835556c31c52816a48e4fb196693206b8afb4408662b3cb575:06e5d8436ac7705b3a90f1631cdd38ec1a3fa49778a9b9f2fa5ebea4e7d560ada7dd26ff42fafa8ba420323742761aca6904940dc21bbef63ff72daab45d430b54e0caa8e63919ca614b2bfd308ccfe50c9ea888e1ee4446d682cb5034627f97b05392c04e835556c31c52816a48e4fb196693206b8afb4408662b3cb575:
|
||||||
|
bf5ba5d6a49dd5ef7b4d5d7d3e4ecc505c01f6ccee4c54b5ef7b40af6a4541401be034f813017b900d8990af45fad5b5214b573bd303ef7a75ef4b8c5c5b9842:1be034f813017b900d8990af45fad5b5214b573bd303ef7a75ef4b8c5c5b9842:16152c2e037b1c0d3219ced8e0674aee6b57834b55106c5344625322da638ecea2fc9a424a05ee9512d48fcf75dd8bd4691b3c10c28ec98ee1afa5b863d1c36795ed18105db3a9aabd9d2b4c1747adbaf1a56ffcc0c533c1c0faef331cdb79d961fa39f880a1b8b1164741822efb15a7259a465bef212855751fab66a897bfa211abe0ea2f2e1cd8a11d80e142cde1263eec267a3138ae1fcf4099db0ab53d64f336f4bcd7a363f6db112c0a2453051a0006f813aaf4ae948a2090619374fa58052409c28ef76225687df3cb2d1b0bfb43b09f47f1232f790e6d8dea759e57942099f4c4bd3390f28afc2098244961465c643fc8b29766af2bcbc5440b86e83608cfc937be98bb4827fd5e6b689adc2e26513db531076a6564396255a09975b7034dac06461b255642e3a7ed75fa9fc265011f5f6250382a84ac268d63ba64:279cace6fdaf3945e3837df474b28646143747632bede93e7a66f5ca291d2c24978512ca0cb8827c8c322685bd605503a5ec94dbae61bbdcae1e49650602bc0716152c2e037b1c0d3219ced8e0674aee6b57834b55106c5344625322da638ecea2fc9a424a05ee9512d48fcf75dd8bd4691b3c10c28ec98ee1afa5b863d1c36795ed18105db3a9aabd9d2b4c1747adbaf1a56ffcc0c533c1c0faef331cdb79d961fa39f880a1b8b1164741822efb15a7259a465bef212855751fab66a897bfa211abe0ea2f2e1cd8a11d80e142cde1263eec267a3138ae1fcf4099db0ab53d64f336f4bcd7a363f6db112c0a2453051a0006f813aaf4ae948a2090619374fa58052409c28ef76225687df3cb2d1b0bfb43b09f47f1232f790e6d8dea759e57942099f4c4bd3390f28afc2098244961465c643fc8b29766af2bcbc5440b86e83608cfc937be98bb4827fd5e6b689adc2e26513db531076a6564396255a09975b7034dac06461b255642e3a7ed75fa9fc265011f5f6250382a84ac268d63ba64:
|
||||||
|
65de297b70cbe80980500af0561a24db50001000125f4490366d8300d3128592ba8e2ad929bdcea538741042b57f2067d3153707a453770db9f3c4ca75504d24:ba8e2ad929bdcea538741042b57f2067d3153707a453770db9f3c4ca75504d24:131d8f4c2c94b153565b86592e770c987a443461b39aa2408b29e213ab057affc598b583739d6603a83fef0afc514721db0e76f9bd1b72b98c565cc8881af5747c0ba6f58c53dd2377da6c0d3aa805620cc4e75d52aabcba1f9b2849e08bd1b6b92e6f06615b814519606a02dc65a8609f5b29e9c2af5a894f7116ef28cfd1e7b76b64061732f7a5a3f8aa4c2e569e627a3f9749aa597be49d6b94436c352dd5fa7b83c92d2610faa32095ca302152d91a3c9776750e758ee8e9e402c6f5385eaa5df23850e54beb1be437a416c7115ed6aa6de13b55482532787e0bee34b83f3084406765635497c931b62a0518f1fbc2b891dc7262c7c6b67eda594fa530d74c9329bad5be94c287fbcde53aa80272b83322613d9368e5904076fdbcc88b2c0e59c10b02c448e00d1b3e7a9c9640feffb9523a8a60e1d83f04a4b8df69153b:7a9b736b01cc92a3349f1a3c32dbd91959825394ff443c567405e899c8185ce8fad9500e1fce89d95a6253c00477435acf04bff993de1b00495def0834ee1f07131d8f4c2c94b153565b86592e770c987a443461b39aa2408b29e213ab057affc598b583739d6603a83fef0afc514721db0e76f9bd1b72b98c565cc8881af5747c0ba6f58c53dd2377da6c0d3aa805620cc4e75d52aabcba1f9b2849e08bd1b6b92e6f06615b814519606a02dc65a8609f5b29e9c2af5a894f7116ef28cfd1e7b76b64061732f7a5a3f8aa4c2e569e627a3f9749aa597be49d6b94436c352dd5fa7b83c92d2610faa32095ca302152d91a3c9776750e758ee8e9e402c6f5385eaa5df23850e54beb1be437a416c7115ed6aa6de13b55482532787e0bee34b83f3084406765635497c931b62a0518f1fbc2b891dc7262c7c6b67eda594fa530d74c9329bad5be94c287fbcde53aa80272b83322613d9368e5904076fdbcc88b2c0e59c10b02c448e00d1b3e7a9c9640feffb9523a8a60e1d83f04a4b8df69153b:
|
||||||
|
0826e7333324e7ec8c764292f6015d4670e9b8d7c4a89e8d909e8ef435d18d15ffb2348ca8a018058be71d1512f376f91e8b0d552581254e107602217395e662:ffb2348ca8a018058be71d1512f376f91e8b0d552581254e107602217395e662:7f9e3e2f03c9df3d21b990f5a4af8295734afe783accc34fb1e9b8e95a0fd837af7e05c13cda0de8fadac9205265a0792b52563bdc2fee766348befcc56b88bbb95f154414fb186ec436aa62ea6fcabb11c017a9d2d15f67e595980e04c9313bc94fbc8c1134c2f40332bc7e311ac1ce11b505f8572ada7fbe196fba822d9a914492fa7185e9f3bea4687200a524c673a1cdf87eb3a140dcdb6a8875613488a2b00adf7175341c1c257635fa1a53a3e21d60c228399eea0991f112c60f653d7148e2c5ceb98f940831f070db1084d79156cc82c46bc9b8e884f3fa81be2da4cdda46bcaa24cc461f76ee647bb0f0f8c15ac5daa795b945e6f85bb310362e48d8095c782c61c52b481b4b002ad06ea74b8d306eff71abf21db710a8913cbe48332be0a0b3f31e0c7a6eba85ce33f357c7aeccd30bfb1a6574408b66fe404d31c3c5:4bac7fabec8724d81ab09ae130874d70b5213492104372f601ae5abb10532799373c4dad215876441f474e2c006be37c3c8f5f6f017d0870414fd276a8f428087f9e3e2f03c9df3d21b990f5a4af8295734afe783accc34fb1e9b8e95a0fd837af7e05c13cda0de8fadac9205265a0792b52563bdc2fee766348befcc56b88bbb95f154414fb186ec436aa62ea6fcabb11c017a9d2d15f67e595980e04c9313bc94fbc8c1134c2f40332bc7e311ac1ce11b505f8572ada7fbe196fba822d9a914492fa7185e9f3bea4687200a524c673a1cdf87eb3a140dcdb6a8875613488a2b00adf7175341c1c257635fa1a53a3e21d60c228399eea0991f112c60f653d7148e2c5ceb98f940831f070db1084d79156cc82c46bc9b8e884f3fa81be2da4cdda46bcaa24cc461f76ee647bb0f0f8c15ac5daa795b945e6f85bb310362e48d8095c782c61c52b481b4b002ad06ea74b8d306eff71abf21db710a8913cbe48332be0a0b3f31e0c7a6eba85ce33f357c7aeccd30bfb1a6574408b66fe404d31c3c5:
|
||||||
|
00ad6227977b5f38ccda994d928bba9086d2daeb013f8690db986648b90c1d4591a4ea005752b92cbebf99a8a5cbecd240ae3f016c44ad141b2e57ddc773dc8e:91a4ea005752b92cbebf99a8a5cbecd240ae3f016c44ad141b2e57ddc773dc8e:cb5bc5b98b2efce43543e91df041e0dbb53ed8f67bf0f197c52b2211e7a45e2e1ec818c1a80e10abf6a43535f5b79d974d8ae28a2295c0a6521763b607d5103c6aef3b2786bd5afd7563695660684337bc3090739fb1cd53a9d644139b6d4caec75bda7f2521fbfe676ab45b98cb317aa7ca79fc54a3d7c578466a6aa64e434e923465a7f211aa0c61681bb8486e90206a25250d3fdae6fb03299721e99e2a914910d91760089b5d281e131e6c836bc2de08f7e02c48d323c647e9536c00ec1039201c0362618c7d47aa8e7b9715ffc439987ae1d31154a6198c5aa11c128f4082f556c99baf103ecadc3b2f3b2ec5b469623bc03a53caf3814b16300aedbda538d676d1f607102639db2a62c446707ce6469bd873a0468225be88b0aef5d4020459b94b32fe2b0133e92e7ba54dd2a5397ed85f966ab39ed0730cca8e7dacb8a336:dc501db79fd782bc88cae792557d5d273f9ba560c7d90037fe84ac879d684f612a77452c4443e95c07b8be192c35769b17bbdfca42280de796d92119d833670dcb5bc5b98b2efce43543e91df041e0dbb53ed8f67bf0f197c52b2211e7a45e2e1ec818c1a80e10abf6a43535f5b79d974d8ae28a2295c0a6521763b607d5103c6aef3b2786bd5afd7563695660684337bc3090739fb1cd53a9d644139b6d4caec75bda7f2521fbfe676ab45b98cb317aa7ca79fc54a3d7c578466a6aa64e434e923465a7f211aa0c61681bb8486e90206a25250d3fdae6fb03299721e99e2a914910d91760089b5d281e131e6c836bc2de08f7e02c48d323c647e9536c00ec1039201c0362618c7d47aa8e7b9715ffc439987ae1d31154a6198c5aa11c128f4082f556c99baf103ecadc3b2f3b2ec5b469623bc03a53caf3814b16300aedbda538d676d1f607102639db2a62c446707ce6469bd873a0468225be88b0aef5d4020459b94b32fe2b0133e92e7ba54dd2a5397ed85f966ab39ed0730cca8e7dacb8a336:
|
||||||
|
1521c6dbd6f724de73eaf7b56264f01035c04e01c1f3eb3cbe83efd26c439ada2f61a26ffb68ba4f6e141529dc2617e8531c7151404808093b4fa7fedaea255d:2f61a26ffb68ba4f6e141529dc2617e8531c7151404808093b4fa7fedaea255d:3e3c7c490788e4b1d42f5cbcae3a9930bf617ebdff447f7be2ac2ba7cd5bcfc015760963e6fe5b956fb7cdb35bd5a17f5429ca664f437f08753a741c2bc8692b71a9115c582a25b2f74d329854d60b7817c079b3523aaff8793c2f72fff8cd10592c54e738df1d6452fb72da131c6731ea5c953c62ea177ac1f4735e5154477387109afae15f3ed6eeb08606e28c81d4386f03b9376924b6ef8d221ee29547f82a7ede48e1dc17723e3d42171eeaf96ac84bedc2a01dd86f4d085734fd69f91b5263e439083ff0318536adff4147308e3aafd1b58bb74f6fb0214a46fdcd3524f18df5a719ce57319e791b4ea606b499bfa57a60e707f94e18f1fed22f91bc79e6364a843f9cbf93825c465e9cae9072bc9d3ec4471f21ab2f7e99a633f587aac3db78ae9666a89a18008dd61d60218554411a65740ffd1ae3adc06595e3b7876407b6:a817ed23ec398a128601c1832dc6af7643bf3a5f517bcc579450fdb4759028f4966164125f6ebd0d6bf86ff298a39c766d0c21fdb0cbfdf81cd0eb1f03cd8a083e3c7c490788e4b1d42f5cbcae3a9930bf617ebdff447f7be2ac2ba7cd5bcfc015760963e6fe5b956fb7cdb35bd5a17f5429ca664f437f08753a741c2bc8692b71a9115c582a25b2f74d329854d60b7817c079b3523aaff8793c2f72fff8cd10592c54e738df1d6452fb72da131c6731ea5c953c62ea177ac1f4735e5154477387109afae15f3ed6eeb08606e28c81d4386f03b9376924b6ef8d221ee29547f82a7ede48e1dc17723e3d42171eeaf96ac84bedc2a01dd86f4d085734fd69f91b5263e439083ff0318536adff4147308e3aafd1b58bb74f6fb0214a46fdcd3524f18df5a719ce57319e791b4ea606b499bfa57a60e707f94e18f1fed22f91bc79e6364a843f9cbf93825c465e9cae9072bc9d3ec4471f21ab2f7e99a633f587aac3db78ae9666a89a18008dd61d60218554411a65740ffd1ae3adc06595e3b7876407b6:
|
||||||
|
17e5f0a8f34751babc5c723ecf339306992f39ea065ac140fcbc397d2dd32c4b4f1e23cc0f2f69c88ef9162ab5f8c59fb3b8ab2096b77e782c63c07c8c4f2b60:4f1e23cc0f2f69c88ef9162ab5f8c59fb3b8ab2096b77e782c63c07c8c4f2b60:c0fad790024019bd6fc08a7a92f5f2ac35cf6432e2eaa53d482f6e1204935336cb3ae65a63c24d0ec6539a10ee18760f2f520537774cdec6e96b55536011daa8f8bcb9cdaf6df5b34648448ac7d7cb7c6bd80d67fbf330f8765297766046a925ab52411d1604c3ed6a85173040125658a32cf4c854ef2813df2be6f3830e5eee5a6163a83ca8849f612991a31e9f88028e50bf8535e11755fad029d94cf25959f6695d09c1ba4315d40f7cf51b3f8166d02faba7511ecd8b1dded5f10cd6843455cff707ed225396c61d0820d20ada70d0c3619ff679422061c9f7c76e97d5a37af61fd62212d2dafc647ebbb979e61d9070ec03609a07f5fc57d119ae64b7a6ef92a5afae660a30ed48d702cc3128c633b4f19060a0578101729ee979f790f45bdbb5fe1a8a62f01a61a31d61af07030450fa0417323e9407bc76e73130e7c69d62e6a7:efe2cb63fe7b4fc98946dc82fb6998e741ed9ce6b9c1a93bb45bc0a7d8396d7405282b43fe363ba5b23589f8e1fae130e157ce888cd72d053d0cc19d257a4300c0fad790024019bd6fc08a7a92f5f2ac35cf6432e2eaa53d482f6e1204935336cb3ae65a63c24d0ec6539a10ee18760f2f520537774cdec6e96b55536011daa8f8bcb9cdaf6df5b34648448ac7d7cb7c6bd80d67fbf330f8765297766046a925ab52411d1604c3ed6a85173040125658a32cf4c854ef2813df2be6f3830e5eee5a6163a83ca8849f612991a31e9f88028e50bf8535e11755fad029d94cf25959f6695d09c1ba4315d40f7cf51b3f8166d02faba7511ecd8b1dded5f10cd6843455cff707ed225396c61d0820d20ada70d0c3619ff679422061c9f7c76e97d5a37af61fd62212d2dafc647ebbb979e61d9070ec03609a07f5fc57d119ae64b7a6ef92a5afae660a30ed48d702cc3128c633b4f19060a0578101729ee979f790f45bdbb5fe1a8a62f01a61a31d61af07030450fa0417323e9407bc76e73130e7c69d62e6a7:
|
||||||
|
0cd7aa7d605e44d5ffb97966b2cb93c189e4c5a85db87fad7ab8d62463c59b594889855fe4116b4913927f47f2273bf559c3b394a983631a25ae597033185e46:4889855fe4116b4913927f47f2273bf559c3b394a983631a25ae597033185e46:28a55dda6cd0844b6577c9d6da073a4dc35cbc98ac158ab54cf88fd20cc87e83c4bba2d74d82ce0f4854ec4db513de400465aaa5eee790bc84f16337072d3a91cde40d6e0df1ba0cc0645f5d5cbbb642381d7b9e211d25267a8acf77d1edb69c3a630f5b133d24f046a81bf22ff03b31d8447e12c3f7b77114a70cbd20bbd08b0b3827a6bbcf90409e344447a7fbc59bdd97d729071f8d71dcc33e6ef2cbab1d411edf13734db1dd9703276f5eb2d6aa2cb8952dd6712bfae809ce08c3aa502b8135713fac0a9c25b1d45b6a5831e02421bba65b81a596efa24b0576bd1dc7fdfb49be762875e81bd540722bc06140b9aa2ef7b84a801e41ded68d4546ac4873d9e7ced649b64fadaf0b5c4b6eb8d036315233f4326ca01e03393050cd027c24f67303fb846bd2c6b3dba06bed0d59a36289d24bd648f7db0b3a81346612593e3ddd18c557:bf9115fd3d02706e398d4bf3b02a82674ff3041508fd39d29f867e501634b9261f516a794f98738d7c7013a3f2f858ffdd08047fb6bf3dddfb4b4f4cbeef300328a55dda6cd0844b6577c9d6da073a4dc35cbc98ac158ab54cf88fd20cc87e83c4bba2d74d82ce0f4854ec4db513de400465aaa5eee790bc84f16337072d3a91cde40d6e0df1ba0cc0645f5d5cbbb642381d7b9e211d25267a8acf77d1edb69c3a630f5b133d24f046a81bf22ff03b31d8447e12c3f7b77114a70cbd20bbd08b0b3827a6bbcf90409e344447a7fbc59bdd97d729071f8d71dcc33e6ef2cbab1d411edf13734db1dd9703276f5eb2d6aa2cb8952dd6712bfae809ce08c3aa502b8135713fac0a9c25b1d45b6a5831e02421bba65b81a596efa24b0576bd1dc7fdfb49be762875e81bd540722bc06140b9aa2ef7b84a801e41ded68d4546ac4873d9e7ced649b64fadaf0b5c4b6eb8d036315233f4326ca01e03393050cd027c24f67303fb846bd2c6b3dba06bed0d59a36289d24bd648f7db0b3a81346612593e3ddd18c557:
|
||||||
|
33371d9e892f9875052ac8e325ba505e7477c1ace24ba7822643d43d0acef3de35929bded27c249c87d8b8d82f59260a575327b546c3a167c69f5992d5b8e006:35929bded27c249c87d8b8d82f59260a575327b546c3a167c69f5992d5b8e006:27a32efba28204be59b7ff5fe488ca158a91d5986091ecc4458b49e090dd37cbfede7c0f46186fabcbdff78d2844155808efffd873ed9c9261526e04e4f7050b8d7bd267a0fe3d5a449378d54a4febbd2f26824338e2aaaf35a32ff0f62504bda5c2e44abc63159f336cf25e6bb40ddb7d8825dff18fd51fc01951eaedcd33707007e1203ca58b4f7d242f8166a907e099932c001bfb1ec9a61e0ef2da4e8446af208201315d69681710d425d2400c387d7b9df321a4aec602b9c656c3e2310bff8756d18b802134b15604f4edc111149a9879e31241dd34f702f4c349617b13529769a772f5e52a89c098e0dca5920667893a250061b17991626eb9319298685be46b6a8b68422444fa5a36bcf3a687e2eccb9322c87dc80165da898930850b98fc863cada1aa99c6d61c451b9ccf4874c7f0e75b0a0c602f044812c71765adaf02025395b0:985ca446ddc007827cc8f2852cbd8115ef8c5975e9d7ce96d74dfed859aa14a4c15254006bea5e08359efe2625d715e0897ee5a16f151203be5010418637de0527a32efba28204be59b7ff5fe488ca158a91d5986091ecc4458b49e090dd37cbfede7c0f46186fabcbdff78d2844155808efffd873ed9c9261526e04e4f7050b8d7bd267a0fe3d5a449378d54a4febbd2f26824338e2aaaf35a32ff0f62504bda5c2e44abc63159f336cf25e6bb40ddb7d8825dff18fd51fc01951eaedcd33707007e1203ca58b4f7d242f8166a907e099932c001bfb1ec9a61e0ef2da4e8446af208201315d69681710d425d2400c387d7b9df321a4aec602b9c656c3e2310bff8756d18b802134b15604f4edc111149a9879e31241dd34f702f4c349617b13529769a772f5e52a89c098e0dca5920667893a250061b17991626eb9319298685be46b6a8b68422444fa5a36bcf3a687e2eccb9322c87dc80165da898930850b98fc863cada1aa99c6d61c451b9ccf4874c7f0e75b0a0c602f044812c71765adaf02025395b0:
|
||||||
|
beedb8073df58f8c1bffbdbd77ec7decb2c82a9babecefc0331507bdc2c2a7e7b27e908b805e296fc30d2e474b060cd50c0f6f520b3671712183bd89d4e733e9:b27e908b805e296fc30d2e474b060cd50c0f6f520b3671712183bd89d4e733e9:35ca57f0f915e5209d54ea4b871ffb585354df1b4a4a1796fbe4d6227d3e1aba5171ed0391a79e83e24d82fdafd15c17b28bf6c94d618c74d65264e58faaacd2902872fdd0efa22e8d2d7ce8e3b8197f0c3615b0a385235fa9fd8e4564ee6e6b1650b4cfb94d872c805c32d4f3a18f966461d3adbb605fa525884f8eb197627396ba4d995d78ac02948a0eaabb58519b9a8e2e7985cd1de2c71d8918d96a0168660ce17cddf364e3ec0d4bd90f2104751a1927ee1d23f3e7a69840ed040b00e5f6e4866ec58813149cc382aebf6162608c79574d553f47230e924a0ef1ebf55d8e1a52abb62a2d7ac86027c7c03cc83fa1949da29e2f3037ab986fd2fffe650e3149babae5a50b1ee9696f3babec72e29697c82422814d272085500fd837fe3c7a973ef4c169af12dd7f02700620bb045bdbf84623f326350570b3cadbc9aea4200b28287e17ab:8c890cccadc7760e1e82e43c44b3dc0b685a48b479ae13cc0a6b0557d0fb1cbabba63d2a96843412ea8d36c50acbf52b92cfb2dce49dc48af6ddcf8ee47a860835ca57f0f915e5209d54ea4b871ffb585354df1b4a4a1796fbe4d6227d3e1aba5171ed0391a79e83e24d82fdafd15c17b28bf6c94d618c74d65264e58faaacd2902872fdd0efa22e8d2d7ce8e3b8197f0c3615b0a385235fa9fd8e4564ee6e6b1650b4cfb94d872c805c32d4f3a18f966461d3adbb605fa525884f8eb197627396ba4d995d78ac02948a0eaabb58519b9a8e2e7985cd1de2c71d8918d96a0168660ce17cddf364e3ec0d4bd90f2104751a1927ee1d23f3e7a69840ed040b00e5f6e4866ec58813149cc382aebf6162608c79574d553f47230e924a0ef1ebf55d8e1a52abb62a2d7ac86027c7c03cc83fa1949da29e2f3037ab986fd2fffe650e3149babae5a50b1ee9696f3babec72e29697c82422814d272085500fd837fe3c7a973ef4c169af12dd7f02700620bb045bdbf84623f326350570b3cadbc9aea4200b28287e17ab:
|
||||||
|
9184ef618816832592bc8eb35f4ffd4ff98dfbf7776c90f2aad212ce7e03351e687b7726010d9bde2c90e573cd2a2a702ff28c4a2af70afc7315c94d575601e5:687b7726010d9bde2c90e573cd2a2a702ff28c4a2af70afc7315c94d575601e5:729eb7e54a9d00c58617af18c345b8dc6e5b4e0f57de2f3c02e54a2ec8f1425ec2e240775b5ab0c10f84ac8bafda4584f7e21c655faecd8030a98906bd68398f26b5d58d92b6cf045e9bd9743c74c9a342ec61ce57f37b981eac4d8bf034608866e985bb68686a68b4a2af88b992a2a6d2dc8ce88bfb0a36cf28bbab7024abfa2bea53313b66c906f4f7cf66970f540095bd0104aa4924dd82e15413c22679f847e48cd0c7ec1f677e005fec0177fbd5c559fc39add613991fbaeae4d24d39d309ef74647f8192cc4c62d0642028c76a1b951f6bc9639deb91ecc08be6043f2109705a42c7eae712649d91d96ccbbfb63d8d0dd6dd112160f61361ecdc6793929ca9aef9ab56944a6fa4a7df1e279eaf58ce8323a9cf62c94279fff7440fbc936baa61489c999330badcb9fc0e184bc5093f330cbb242f71fb378738fea10511dd438364d7f76bcc:b3c24e75132c563475422d5ea412b5c1e8e6e5ea1c08ead1393c412da134c9a1638284ea7e2ca032fe3d3e32a9066a8c8839903f6ef46e966bb5e492d8c2aa00729eb7e54a9d00c58617af18c345b8dc6e5b4e0f57de2f3c02e54a2ec8f1425ec2e240775b5ab0c10f84ac8bafda4584f7e21c655faecd8030a98906bd68398f26b5d58d92b6cf045e9bd9743c74c9a342ec61ce57f37b981eac4d8bf034608866e985bb68686a68b4a2af88b992a2a6d2dc8ce88bfb0a36cf28bbab7024abfa2bea53313b66c906f4f7cf66970f540095bd0104aa4924dd82e15413c22679f847e48cd0c7ec1f677e005fec0177fbd5c559fc39add613991fbaeae4d24d39d309ef74647f8192cc4c62d0642028c76a1b951f6bc9639deb91ecc08be6043f2109705a42c7eae712649d91d96ccbbfb63d8d0dd6dd112160f61361ecdc6793929ca9aef9ab56944a6fa4a7df1e279eaf58ce8323a9cf62c94279fff7440fbc936baa61489c999330badcb9fc0e184bc5093f330cbb242f71fb378738fea10511dd438364d7f76bcc:
|
||||||
|
354e13152ee1fe748a1252204c6527bdc1b1eb2eb53678150e6359924708d812d45ff6c5fb83e7bb9669aa8960deb7dbc665c988439b6c9ef672c6811dc8bcf6:d45ff6c5fb83e7bb9669aa8960deb7dbc665c988439b6c9ef672c6811dc8bcf6:8e5fccf66b1ba6169cb685733d9d0e0190361c90bcab95c163285a97fe356d2bdcde3c9380268805a384d063da09ccd9969cc3ff7431e60a8e9f869cd62faa0e356151b280bc526e577c2c538c9a724dc48bf88b70321d7e1eeedb3c4af706748c942e67bdabdb41bec2977b1523069e31e29b76300288f88a51b384b80cc2526f1679340ddec3881f5cd28b0378d9cd0a812b68dd3f68f7a23e1b54bee7466ac765cf38df04d67441dfa498c4bffc52045fa6d2dbcdbfa33dfaa77644ffccef0decdb6790c70a0d734ec287cc338cb5a909c0055189301169c4f7702c05c0911a27b16ef9ed934fa6a0ca7b13e413523422535647968030edc40cd73e7d6b345b7581f438316d68e3cd292b846d3f4f7c4862bc7e6b3fb89a27f6f60cd7db2e34ec9aae1013fe37acff8ad888cb9a593ef5e621eae5186c58b31dcfde22870e336d33f440f6b8d49a:de2b46e65f3decef34332e500f2e11306fbdcf1be85a1c1ee68ba3045dcec2c7be608d22927da1f44c0e2083ae622cf3c29d893887994efcfa2ca594f5051f038e5fccf66b1ba6169cb685733d9d0e0190361c90bcab95c163285a97fe356d2bdcde3c9380268805a384d063da09ccd9969cc3ff7431e60a8e9f869cd62faa0e356151b280bc526e577c2c538c9a724dc48bf88b70321d7e1eeedb3c4af706748c942e67bdabdb41bec2977b1523069e31e29b76300288f88a51b384b80cc2526f1679340ddec3881f5cd28b0378d9cd0a812b68dd3f68f7a23e1b54bee7466ac765cf38df04d67441dfa498c4bffc52045fa6d2dbcdbfa33dfaa77644ffccef0decdb6790c70a0d734ec287cc338cb5a909c0055189301169c4f7702c05c0911a27b16ef9ed934fa6a0ca7b13e413523422535647968030edc40cd73e7d6b345b7581f438316d68e3cd292b846d3f4f7c4862bc7e6b3fb89a27f6f60cd7db2e34ec9aae1013fe37acff8ad888cb9a593ef5e621eae5186c58b31dcfde22870e336d33f440f6b8d49a:
|
||||||
|
7ff62d4b3c4d99d342d4bb401d726b21e99f4ef592149fc311b68761f5567ff67fdfdb9eca29d3f01d9486d7e112ce03aa37b91326a4283b9c03999c5eda099a:7fdfdb9eca29d3f01d9486d7e112ce03aa37b91326a4283b9c03999c5eda099a:99c44c796572a4823fc6c3807730839173774c05dbfc1492ed0d00509a95a1de37274b3135ed0456a1718e576597dc13f2a2ab37a45c06cbb4a2d22afad4d5f3d90ab3d8da4dcdaa06d44f2219088401c5dceee26055c4782f78d7d63a380608e1bef89eeef338c2f0897da106fafce2fb2ebc5db669c7c172c9cfe77d3109d239fe5d005c8ee751511b5a88317c729b0d8b70b52f6bd3cda2fe865c77f36e4f1b635f336e036bd718bec90ee78a802811510c4058c1ba364017253aa842922e1dd7d7a0f0fc9c69e43fc4eaeffaaf1ae5fa5d2d73b43079617baba030923fe5b13d2c1c4fe6fac3f2db74e2020a734b6121a0302fce820ba0580ce6135348fdf0632e0008df03ee112168f5cfa0037a26a1f69b1f1317edf2a3ab367455a77e00691215d7aa3133c2159d3da2b134cf04f0defbf07a6064011e64dd14d4f8f064356655428804c2771a:058f79927fbf6178724815c7b11c63baaa90bcc15d7272be082f8a9141861c816433055f6cf6491424853f9ec78bb91ace913a93411b4e5ed58bc4ba5715c60a99c44c796572a4823fc6c3807730839173774c05dbfc1492ed0d00509a95a1de37274b3135ed0456a1718e576597dc13f2a2ab37a45c06cbb4a2d22afad4d5f3d90ab3d8da4dcdaa06d44f2219088401c5dceee26055c4782f78d7d63a380608e1bef89eeef338c2f0897da106fafce2fb2ebc5db669c7c172c9cfe77d3109d239fe5d005c8ee751511b5a88317c729b0d8b70b52f6bd3cda2fe865c77f36e4f1b635f336e036bd718bec90ee78a802811510c4058c1ba364017253aa842922e1dd7d7a0f0fc9c69e43fc4eaeffaaf1ae5fa5d2d73b43079617baba030923fe5b13d2c1c4fe6fac3f2db74e2020a734b6121a0302fce820ba0580ce6135348fdf0632e0008df03ee112168f5cfa0037a26a1f69b1f1317edf2a3ab367455a77e00691215d7aa3133c2159d3da2b134cf04f0defbf07a6064011e64dd14d4f8f064356655428804c2771a:
|
||||||
|
6cabadd03f8a2e6ebab96a74f80e18164e4d1b6baa678f5a82e25604af989aaf2a4a3179564194e00100c18bc35351d8b135bbae5b32b28fce1d7b6766ca4b32:2a4a3179564194e00100c18bc35351d8b135bbae5b32b28fce1d7b6766ca4b32:279f78cf3b9ccfc6e1b01e1a82f50ed172e9a8e1e702bb15661dd7dc3a456ff7a7a7fdfb081db3867079630c7f70fd753292ec60ecbf50632e9aa45b996505c66e6dc3c6ae892e21b6a8705e4bbae8f16a3378554b31fdb0139dcd15c96a8a7e4b88756a86d18db5dc74fd7691197dd88e2c7d5df52b049344cdc477c9cd7e89eda99ccfb1d00814d0152b9654df3279372ca5f18b1c946f2894a76b079ddb1c3cd61fbb969aeec9193a6b88fb7d136c07f9821e5c1074b4e93bcaf6fa14d0d1d7e1707589d77ec1337206e53a1f06cc26672ff95c13d5ff444766931ba30a0afdcdadd2098e9c41fd87a3f23cd16dbb0efbf8092ce33e327f42610990e1cee6cb8e54951aa081e69765ae4009aeed758e768de50c23d9a22b4a06dc4d19fc8cbd0cdef4c983461755d0a3b5d6a9c12253e09568339ff7e5f78c5fdf7ec89f9186a621a8c0eed11b67022e:4e65c6c1d493045e8a9250e397c1d1d30ffed24db66a8961aa458f8f0fcb760c39fe8657d7ab8f84000b96d519717cff71f926522c1efec7f8b2624eae55f60c279f78cf3b9ccfc6e1b01e1a82f50ed172e9a8e1e702bb15661dd7dc3a456ff7a7a7fdfb081db3867079630c7f70fd753292ec60ecbf50632e9aa45b996505c66e6dc3c6ae892e21b6a8705e4bbae8f16a3378554b31fdb0139dcd15c96a8a7e4b88756a86d18db5dc74fd7691197dd88e2c7d5df52b049344cdc477c9cd7e89eda99ccfb1d00814d0152b9654df3279372ca5f18b1c946f2894a76b079ddb1c3cd61fbb969aeec9193a6b88fb7d136c07f9821e5c1074b4e93bcaf6fa14d0d1d7e1707589d77ec1337206e53a1f06cc26672ff95c13d5ff444766931ba30a0afdcdadd2098e9c41fd87a3f23cd16dbb0efbf8092ce33e327f42610990e1cee6cb8e54951aa081e69765ae4009aeed758e768de50c23d9a22b4a06dc4d19fc8cbd0cdef4c983461755d0a3b5d6a9c12253e09568339ff7e5f78c5fdf7ec89f9186a621a8c0eed11b67022e:
|
||||||
|
0fa0c32c3ae34be51b92f91945405981a8e202488558a8e220c288c7d6a5532dd6aee62bd91fc9453635ffcc02b2f38dcab13285140380580ccdff0865df0492:d6aee62bd91fc9453635ffcc02b2f38dcab13285140380580ccdff0865df0492:53f44be0e5997ff07264cb64ba1359e2801def8755e64a2362bddaf597e672d021d34fface6d97e0f2b1f6ae625fd33d3c4f6e9ff7d0c73f1da8defb23f324975e921bb2473258177a16612567edf7d5760f3f3e3a6d26aaabc5fde4e2043f73fa70f128020933b1ba3b6bd69498e9503ea670f1ed880d3651f2e4c59e79cabc86e9b703394294112d5d8e213c317423b525a6df70106a9d658a262028b5f45100cb77d1150d8fe461eed434f241015f3276ad7b09a291b4a7f35e3c30051cbf13b1d4a7fa0c81a50f939e7c49673afdc87883c9e3e61f5a1df03755470fda74bf23ea88676b258a97a280d5f90b52b714b596035bae08c8d0fe6d94f8949559b1f27d7116cf59dd3cfbf18202a09c13f5c4fbc8d97225492887d32870c2297e34debd9876d6d01ac27a16b088b079079f2b20feb02537cda314c43cb2dca371b9df37ed11ec97e1a7a6993a:7e9ab85ee94fe4b35dcb545329a0ef25923de5c9dc23e7df1a7e77ab0dcfb89e03f4e785ca6429cb2b0df50da6230f733f00f33a45c4e576cd40bdb84f1ae00153f44be0e5997ff07264cb64ba1359e2801def8755e64a2362bddaf597e672d021d34fface6d97e0f2b1f6ae625fd33d3c4f6e9ff7d0c73f1da8defb23f324975e921bb2473258177a16612567edf7d5760f3f3e3a6d26aaabc5fde4e2043f73fa70f128020933b1ba3b6bd69498e9503ea670f1ed880d3651f2e4c59e79cabc86e9b703394294112d5d8e213c317423b525a6df70106a9d658a262028b5f45100cb77d1150d8fe461eed434f241015f3276ad7b09a291b4a7f35e3c30051cbf13b1d4a7fa0c81a50f939e7c49673afdc87883c9e3e61f5a1df03755470fda74bf23ea88676b258a97a280d5f90b52b714b596035bae08c8d0fe6d94f8949559b1f27d7116cf59dd3cfbf18202a09c13f5c4fbc8d97225492887d32870c2297e34debd9876d6d01ac27a16b088b079079f2b20feb02537cda314c43cb2dca371b9df37ed11ec97e1a7a6993a:
|
||||||
|
7b06f88026fa86f39fce2426f67cc5996bedd0cfc4b5ebb1b5e3edbb47e080aa3f1469ee6a2e7867e2e9012d402cf5a4861497c01df879a1deb1c539830b58de:3f1469ee6a2e7867e2e9012d402cf5a4861497c01df879a1deb1c539830b58de:71175d4e21721297d9176d817f4e785d9600d923f987fe0b26fd79d33a5ea5d1e818b71f0f92b8c73afddabdcc27f6d16e26aafa874cfd77a00e06c36b041487582bb933760f88b419127345776ea418f83522254fed33819bc5c95f8f8404cc144ebf1486c88515409d3433aaf519d9920f5256e629419e9a95580a35b069b8d25533dfcbc98ad36404a951808e01378c03266326d120046975fde07daef3266caacd821c1403499d7fdf17c033c8d8c3f28f162b5f09dfdaca06285f00c6cb986dfdf5151aa6639608b5b13e78d65a4368585b16138754fbd113835a686cd066c2b89bb0953c24d50e77bf0fc457c1e0fcf5d44da8db9a88f062be3b688d5cdcff1d1c00e81ec9d413882295b341fee8fa427dc109adeb5f284eec202f1bef115bf96b1782d3ccdeb682b69bf92d170c007d5df80e1ed962f677dc24a145a1e4e829e8dec0104e5f78365944:42f133e34e3eb7032a133ed781537ec62e44a5ce8381e5e0bf9e13a914a4b2c757811d6d3b1e86672424ea4230d10f7c610abb7069e61e319b4066a2bd7bc90071175d4e21721297d9176d817f4e785d9600d923f987fe0b26fd79d33a5ea5d1e818b71f0f92b8c73afddabdcc27f6d16e26aafa874cfd77a00e06c36b041487582bb933760f88b419127345776ea418f83522254fed33819bc5c95f8f8404cc144ebf1486c88515409d3433aaf519d9920f5256e629419e9a95580a35b069b8d25533dfcbc98ad36404a951808e01378c03266326d120046975fde07daef3266caacd821c1403499d7fdf17c033c8d8c3f28f162b5f09dfdaca06285f00c6cb986dfdf5151aa6639608b5b13e78d65a4368585b16138754fbd113835a686cd066c2b89bb0953c24d50e77bf0fc457c1e0fcf5d44da8db9a88f062be3b688d5cdcff1d1c00e81ec9d413882295b341fee8fa427dc109adeb5f284eec202f1bef115bf96b1782d3ccdeb682b69bf92d170c007d5df80e1ed962f677dc24a145a1e4e829e8dec0104e5f78365944:
|
||||||
|
c3f5e149968a24f4de9119531975f443015ccca305d7119ed4749e8bf6d94fc739aaccdb948a4038538a4588322f806bb129b5876c4bec51271afe4f49690045:39aaccdb948a4038538a4588322f806bb129b5876c4bec51271afe4f49690045:c46370e37f2e0cadcf93402f1f0cb048f52881ba750b7a43f56ab11ce348732fb57e7f9aaf8dfcbe455e14e983c248d026a27e7f148d5db5a53f94635702b895127771047a876d14107386c5e0ff8933345bbd7a936d990d33efa28c2ec4e4864ffd2ff576f7c88f954cfc1c459e883bb712dae3cdf6632066f1f4d13a509615b3360cadc5a307f23e52a51b40a6feebe0b18d0e9ee4e348f33cd81a8def222f6a59b12861d335bd9af85cc004be46f1d3a424f4870ae9dc587e5a4ade136b9370649348c33ac3bf1febeebffea37085ed59cac9d9e696470b234609e9a10a9d431ff91e69cb5135fd117ff58a36539744ebe70cea6973c00c7a4d57b62f4a7136d731b8e46ff18ec0ed69070031905075d8541d568cfce6eeb76242b7819a7b6a93552111bb88f165527cfa6966d39fcbe0a7dea008e39c7a3e577ab307cd1d0ea326833d52654e172955f3fcd4:5fa2b531677b00b85b0a313cbd479f55f4ab3ec5cfce5e454d2b74176ccc3399c899f9d6b51ed4c1e76185ac9fe730c4b4014044f7041185bc3c85722eb2ea02c46370e37f2e0cadcf93402f1f0cb048f52881ba750b7a43f56ab11ce348732fb57e7f9aaf8dfcbe455e14e983c248d026a27e7f148d5db5a53f94635702b895127771047a876d14107386c5e0ff8933345bbd7a936d990d33efa28c2ec4e4864ffd2ff576f7c88f954cfc1c459e883bb712dae3cdf6632066f1f4d13a509615b3360cadc5a307f23e52a51b40a6feebe0b18d0e9ee4e348f33cd81a8def222f6a59b12861d335bd9af85cc004be46f1d3a424f4870ae9dc587e5a4ade136b9370649348c33ac3bf1febeebffea37085ed59cac9d9e696470b234609e9a10a9d431ff91e69cb5135fd117ff58a36539744ebe70cea6973c00c7a4d57b62f4a7136d731b8e46ff18ec0ed69070031905075d8541d568cfce6eeb76242b7819a7b6a93552111bb88f165527cfa6966d39fcbe0a7dea008e39c7a3e577ab307cd1d0ea326833d52654e172955f3fcd4:
|
||||||
|
42305c9302f45ea6f87e26e2208fd94b3c4ad037b1b6c83cf6677aa1096a013c3b97b1f11ce45ba46ffbb25b76bfc5ad7b77f90cc69ed76115dea4029469d587:3b97b1f11ce45ba46ffbb25b76bfc5ad7b77f90cc69ed76115dea4029469d587:d110828d449198d675e74e8e39439fd15e75bf2cc1f430abfb245836885bafc420f754b89d2fbbf6dd3490792e7a4f766073cfe3b302d089831ace869e2730fde45c2121ec3ef217aa9c43fa7cc7e9ed0a01ad9f1d2fc3613638ca9fc193c98b37455bf5dbf8f38b64708dfdca6c21f0975f1017c5da5f6434bda9f033cec2a631ab50318e017b170b240bf01eb8b36c7e1cb59e7736ac34444208132a8f59e4f313d65d849c6a4fdf13e20ecaee3823e589a171b39b2489497b06e6ff58c2c9f1dc5d3aa3bd10e6443e22d42d07b783f79fd43a46e1cde314b663a95f7246dea131fcd46d1dc333c5454f86b2c4e2e424dea405cc2230d4dcd39a2eab2f92845cf6a7994192063f1202749ef52dcb96f2b79ed6a98118ca0b99ba2285490860eb4c61ab78b9ddc6acc7ad883fa5e96f9d029171223abf7573e36230e0a81f6c1311151473ee264f4b842e923dcb3b:18d05e5d01668e83f40fa3bbee28b388acf318d1b0b5ad668c672f345c8eda14c2f884cd2a9039459ce0810bc5b580fe70d3964a43edb49e73a6ff914bbf040cd110828d449198d675e74e8e39439fd15e75bf2cc1f430abfb245836885bafc420f754b89d2fbbf6dd3490792e7a4f766073cfe3b302d089831ace869e2730fde45c2121ec3ef217aa9c43fa7cc7e9ed0a01ad9f1d2fc3613638ca9fc193c98b37455bf5dbf8f38b64708dfdca6c21f0975f1017c5da5f6434bda9f033cec2a631ab50318e017b170b240bf01eb8b36c7e1cb59e7736ac34444208132a8f59e4f313d65d849c6a4fdf13e20ecaee3823e589a171b39b2489497b06e6ff58c2c9f1dc5d3aa3bd10e6443e22d42d07b783f79fd43a46e1cde314b663a95f7246dea131fcd46d1dc333c5454f86b2c4e2e424dea405cc2230d4dcd39a2eab2f92845cf6a7994192063f1202749ef52dcb96f2b79ed6a98118ca0b99ba2285490860eb4c61ab78b9ddc6acc7ad883fa5e96f9d029171223abf7573e36230e0a81f6c1311151473ee264f4b842e923dcb3b:
|
||||||
|
c57a43dcd7bab8516009546918d71ad459b7345efdca8d4f19929875c839d7222083b444236b9ab31d4e00c89d55c6260fee71ac1a47c4b5ba227404d382b82d:2083b444236b9ab31d4e00c89d55c6260fee71ac1a47c4b5ba227404d382b82d:a4f6d9c281cf81a28a0b9e77499aa24bde96cc1264374491c008294ee0af6f6e4bbb686396f59068d358e30fe9992db0c6f16680a1c71e27a4a907ac607d39bdc3258c7956482fb37996f4beb3e5051b8148019a1c256e2ee999ebc8ce64c54e07fedb4fbd8953ebd93b7d69ce5a0082edd6209d12d3619b4fd2eae916461f72a4ce727157251a19209bbff9fbdbd289436f3fcacc6b4e1318521a47839cba4b14f7d7a21e7b5d6b6a753d5804afcd2b1eb7779b92abab8afa8aa4fa51caec0b85dcd0fc2a0676036d3f56630a831ffeb502861dd89161c708a9c006c73c930ce5b94756426ff18aa112fb4eb9a68500b48d4eedbd4167b6ffd0a11d49443a173ce9d949436748fc0634f06bb08b8f3423f4463dba7b4d199b64df578117f0a2645f0b2a1e2ada27d286f76733f25b82ed1d48a5c3898d4ad621e50ed9060daad40a39532e4d1bf162ce36804d5d4e2d:1edef9bc036971f1fa88edf45393c802e6c1a1631c8a06871a09a320821dce40beca97e53a0361a955a4c6d60b8ca8e400c81340911ccb4f56284041cdbb1804a4f6d9c281cf81a28a0b9e77499aa24bde96cc1264374491c008294ee0af6f6e4bbb686396f59068d358e30fe9992db0c6f16680a1c71e27a4a907ac607d39bdc3258c7956482fb37996f4beb3e5051b8148019a1c256e2ee999ebc8ce64c54e07fedb4fbd8953ebd93b7d69ce5a0082edd6209d12d3619b4fd2eae916461f72a4ce727157251a19209bbff9fbdbd289436f3fcacc6b4e1318521a47839cba4b14f7d7a21e7b5d6b6a753d5804afcd2b1eb7779b92abab8afa8aa4fa51caec0b85dcd0fc2a0676036d3f56630a831ffeb502861dd89161c708a9c006c73c930ce5b94756426ff18aa112fb4eb9a68500b48d4eedbd4167b6ffd0a11d49443a173ce9d949436748fc0634f06bb08b8f3423f4463dba7b4d199b64df578117f0a2645f0b2a1e2ada27d286f76733f25b82ed1d48a5c3898d4ad621e50ed9060daad40a39532e4d1bf162ce36804d5d4e2d:
|
||||||
|
2dddb6b8fd04fa90ece1a709f8418f2e5d0c9c43afe7cfce19e6ad15a73476f78059de6a7c4776489ecc2e7d707ffce30285bf30a23f78d72db49cfd6ed0d492:8059de6a7c4776489ecc2e7d707ffce30285bf30a23f78d72db49cfd6ed0d492:474baa590a4cd72d5424e51d8257b3d44325bc4c5063a0033c86ebbe99ed7212184c19944d082a115379dd4cece973faa0bca6485bd25f3744a719e70aa0291e1b5a96e637c140616a98263357c76b6eb0083fe51414e386870d0fdc7dd9abe4ff6fb5bbf1e7b15dac3e08e2615f655c3104ceb32a4cc2c9e9c43cf282d346ac253ccc46b635ae040973b49735720ffb890469a567c5824e0c00d7ccd5509a718092a906461c4d6163eaf422418f5fc6e009fc3f529ac61a2f89bb8e0ed45d940c4c2331ff8d8e1d6d58d417d8fc2656a02e8701aee75aed918724eebe4a2cf4744c5c401e217023df68a6f6a0228bd05a679a697d8de7036b9ed269090d3c65486afb91e27954eb15b964665ede7ad008f12fb3a9d0e69c13b4254f43819e0818a4195f68b8a38ae81f3fcb1879c95ab4cd0ffc38e381089260cca967ace5a085b457ab5eb363852101377570f9ac9e38:c634ea7bf72e895a2e796e2834201415b8b45e05e045559284eb9052c0e84f62a5a9f0c9764f7576788c7228b19ef517c195497325a48a9344b147c12fd75509474baa590a4cd72d5424e51d8257b3d44325bc4c5063a0033c86ebbe99ed7212184c19944d082a115379dd4cece973faa0bca6485bd25f3744a719e70aa0291e1b5a96e637c140616a98263357c76b6eb0083fe51414e386870d0fdc7dd9abe4ff6fb5bbf1e7b15dac3e08e2615f655c3104ceb32a4cc2c9e9c43cf282d346ac253ccc46b635ae040973b49735720ffb890469a567c5824e0c00d7ccd5509a718092a906461c4d6163eaf422418f5fc6e009fc3f529ac61a2f89bb8e0ed45d940c4c2331ff8d8e1d6d58d417d8fc2656a02e8701aee75aed918724eebe4a2cf4744c5c401e217023df68a6f6a0228bd05a679a697d8de7036b9ed269090d3c65486afb91e27954eb15b964665ede7ad008f12fb3a9d0e69c13b4254f43819e0818a4195f68b8a38ae81f3fcb1879c95ab4cd0ffc38e381089260cca967ace5a085b457ab5eb363852101377570f9ac9e38:
|
||||||
|
5547f1004baedfce5cfc0850b05302374aad24f6163994ecd751df3af3c106207ce620787385ee1951ac49a77352ee0d6f8c5cd47df74e9e3216a6324fc7cf7f:7ce620787385ee1951ac49a77352ee0d6f8c5cd47df74e9e3216a6324fc7cf7f:a6c17eeb5b8066c2cd9a89667317a945a0c7c96996e77ae854c509c6cd0631e922ad04503af87a3c4628adafed7600d071c078a22e7f64bda08a362b38b26ca15006d38acf532d0dedea4177a2d33f06956d80e963848ec791b2762fa99449b4f1a1ed9b3f2580be3ac7d7f52fb14421d6222ba76f807750c6cbb0b16f0895fc73d9dfc587e1a9e5d1e58375fbab705b8f0c1fd7df8b3ad446f2f08459e7ed1af59556fbc966dc249c1cf604f3e677c8a09d4363608774bf3811bef0642748c55c516c7a580fa3499050acb30eed870d0d91174cb623e98c3ad121cf81f04e57d49b008424a98a31eeaaf5f38e000f903d48d215ed52f862d636a5a73607de85760167267efe30f8a26ebc5aa0c09f5b258d3361ca69d1d7ee07b59648179ab2170ec50c07f6616f216872529421a6334a4a1ed3d2671ef47bc9a92afb58314e832db8a9003408a0487503fe4f67770dd4b6:29df3ad589009c667baa5e72dabb4e53cb7876de4e7efe5cc21ead7fa878db57f97c1103ddb39a861eb88653c1d4ec3b4306e4584b47b8bc90423119e7e4af00a6c17eeb5b8066c2cd9a89667317a945a0c7c96996e77ae854c509c6cd0631e922ad04503af87a3c4628adafed7600d071c078a22e7f64bda08a362b38b26ca15006d38acf532d0dedea4177a2d33f06956d80e963848ec791b2762fa99449b4f1a1ed9b3f2580be3ac7d7f52fb14421d6222ba76f807750c6cbb0b16f0895fc73d9dfc587e1a9e5d1e58375fbab705b8f0c1fd7df8b3ad446f2f08459e7ed1af59556fbc966dc249c1cf604f3e677c8a09d4363608774bf3811bef0642748c55c516c7a580fa3499050acb30eed870d0d91174cb623e98c3ad121cf81f04e57d49b008424a98a31eeaaf5f38e000f903d48d215ed52f862d636a5a73607de85760167267efe30f8a26ebc5aa0c09f5b258d3361ca69d1d7ee07b59648179ab2170ec50c07f6616f216872529421a6334a4a1ed3d2671ef47bc9a92afb58314e832db8a9003408a0487503fe4f67770dd4b6:
|
||||||
|
3dd7203c237aefe9e38a201ff341490179905f9f100828da18fcbe58768b5760f067d7b2ff3a957e8373a7d42ef0832bcda84ebf287249a184a212a94c99ea5b:f067d7b2ff3a957e8373a7d42ef0832bcda84ebf287249a184a212a94c99ea5b:db28ed31ac04b0c2decee7a6b24fc9a082cc262ca7ccf2a247d6372ec3e9120ecedb4542ea593fea30335c5ab9dd318a3b4fd5834299cf3f53d9ef46137b273c390ec3c26a0b4470d0d94b77d82cae4b24587837b167bb7f8166710baeb3ee70af797316cb7d05fa57e468ae3f0bd449404d8528808b41fcca62f5e0a2aa5d8f3acab008cc5f6e5ab02777bdcde87f0a10ef06a4bb37fe02c94815cf76bfb8f5cdd865cc26dcb5cf492edfd547b535e2e6a6d8540956dcba62cfea19a9474406e934337e454270e01036ac45793b6b8aceda187a08d56a2ce4e98f42ea375b101a6b9fcb4231d171aa463eeb43586a4b82a387bcddaf71a80fd5c1f7292efc2bd8e70c11eaa817106061b6c461c4883d613cc06c7e2a03f73d90fc55cdc07265eefd36be72270383d6c676cae37c93691f1ae3d927b3a1cd963e4229757ae5231eea73a9f71515628305410ac2593b325cc631:4c036935a96abc0d050d907bedbe9946fb97439f039c742e051ccf09add7df44d17da98c2ca01bdc2424da1e4debf347f8fff48ac8030d2cc07f9575c044be04db28ed31ac04b0c2decee7a6b24fc9a082cc262ca7ccf2a247d6372ec3e9120ecedb4542ea593fea30335c5ab9dd318a3b4fd5834299cf3f53d9ef46137b273c390ec3c26a0b4470d0d94b77d82cae4b24587837b167bb7f8166710baeb3ee70af797316cb7d05fa57e468ae3f0bd449404d8528808b41fcca62f5e0a2aa5d8f3acab008cc5f6e5ab02777bdcde87f0a10ef06a4bb37fe02c94815cf76bfb8f5cdd865cc26dcb5cf492edfd547b535e2e6a6d8540956dcba62cfea19a9474406e934337e454270e01036ac45793b6b8aceda187a08d56a2ce4e98f42ea375b101a6b9fcb4231d171aa463eeb43586a4b82a387bcddaf71a80fd5c1f7292efc2bd8e70c11eaa817106061b6c461c4883d613cc06c7e2a03f73d90fc55cdc07265eefd36be72270383d6c676cae37c93691f1ae3d927b3a1cd963e4229757ae5231eea73a9f71515628305410ac2593b325cc631:
|
||||||
|
282775df9ebbd7c5a65f3a2b096e36ee64a8f8ea719da77758739e4e7476111da2b49646033a13937cad6b0e914e3cec54989c252ca5643d076555d8c55e56e0:a2b49646033a13937cad6b0e914e3cec54989c252ca5643d076555d8c55e56e0:14cc50c2973ea9d0187a73f71cb9f1ce07e739e049ec2b27e6613c10c26b73a2a966e01ac3be8b505aeaad1485c1c2a3c6c2b00f81b9e5f927b73bfd498601a7622e8544837aad02e72bf72196dc246902e58af253ad7e025e3666d3bfc46b5b02f0eb4a37c9554992abc8651de12fd813177379bb0ce172cd8aaf937f979642bc2ed7c7a430cb14c3cd3101b9f6b91ee3f542acdf017f8c2116297f4564768f4db95dad8a9bcdc8da4d8fb13ef6e2da0b1316d3c8c2f3ed836b35fe2fd33effb409e3bc1b0f85225d2a1de3bfc2d20563946475c4d7ca9fddbaf59ad8f8961d287ae7dd803e7af1fa612329b1bdc04e225600ae731bc01ae0925aed62ac50d46086f3646cf47b072f0d3b044b36f85cec729a8bb2b92883ca4dfb34a8ee8a0273b31af50982bb6131bfa11d55504b1f6f1a0a00438ca26d8ab4f48bcddc9d5a38851abede4151d5b70d720732a00abea2c8b979:15763973859402907d8dcb86adc24a2a168ba3abf2246173d6348afed51ef60b0c0edeff4e10bcef4c6e5778c8bc1f5e9ee0237373445b455155d23de127a20214cc50c2973ea9d0187a73f71cb9f1ce07e739e049ec2b27e6613c10c26b73a2a966e01ac3be8b505aeaad1485c1c2a3c6c2b00f81b9e5f927b73bfd498601a7622e8544837aad02e72bf72196dc246902e58af253ad7e025e3666d3bfc46b5b02f0eb4a37c9554992abc8651de12fd813177379bb0ce172cd8aaf937f979642bc2ed7c7a430cb14c3cd3101b9f6b91ee3f542acdf017f8c2116297f4564768f4db95dad8a9bcdc8da4d8fb13ef6e2da0b1316d3c8c2f3ed836b35fe2fd33effb409e3bc1b0f85225d2a1de3bfc2d20563946475c4d7ca9fddbaf59ad8f8961d287ae7dd803e7af1fa612329b1bdc04e225600ae731bc01ae0925aed62ac50d46086f3646cf47b072f0d3b044b36f85cec729a8bb2b92883ca4dfb34a8ee8a0273b31af50982bb6131bfa11d55504b1f6f1a0a00438ca26d8ab4f48bcddc9d5a38851abede4151d5b70d720732a00abea2c8b979:
|
||||||
|
4730a5cf9772d7d6665ba787bea4c95252e6ecd63ec62390547bf100c0a46375f9f094f7cc1d40f1926b5b22dce465784468b20ab349bc6d4fdf78d0042bbc5b:f9f094f7cc1d40f1926b5b22dce465784468b20ab349bc6d4fdf78d0042bbc5b:e7476d2e668420e1b0fadfbaa54286fa7fa890a87b8280e26078152295e1e6e55d1241435cc430a8693bb10cde4643f59cbfcc256f45f5090c909a14c7fc49d37bfc25af11e8f4c83f4c32d4aabf43b20fa382bb6622a1848f8ffc4dff3408bb4ec7c67a35b4cdaee5e279c0fc0a66093a9f36a60fdd65e6334a804e845c8530b6fda363b5640337d027243ccfb3c177f43e717896e46ead7f72ca06aa0ff1e77247121baf48be9a445f729ca1390fc46151cbd33fcbd7373f27a6ba55c92cbf6945b09b44b9a4e5800d403070ae66048997b2197f02181a097e563f9b9acc841139258a258bc610d3bd891637356b2edc8c184c35c65af91aaf7b1c16d74a5f5f862548139254ecf550631d5f8849afdb5b64cf366ff2633a93f3a18c39b5150245fb5f33c9e4e2d94af6963a70b88f9e7e519f8fa2a0f2e3749de883d0e6f052a949d0fc7153a8693f6d801d7352eb2f7a465c0e:552c7347bdfe131646ce0932d82a36d2c1b76d7c30ee890e0592e19f9d18b9a56f48d7a9b68c017da6b550c943af4a907baf317e419fbbc96f6cf4bfad42de00e7476d2e668420e1b0fadfbaa54286fa7fa890a87b8280e26078152295e1e6e55d1241435cc430a8693bb10cde4643f59cbfcc256f45f5090c909a14c7fc49d37bfc25af11e8f4c83f4c32d4aabf43b20fa382bb6622a1848f8ffc4dff3408bb4ec7c67a35b4cdaee5e279c0fc0a66093a9f36a60fdd65e6334a804e845c8530b6fda363b5640337d027243ccfb3c177f43e717896e46ead7f72ca06aa0ff1e77247121baf48be9a445f729ca1390fc46151cbd33fcbd7373f27a6ba55c92cbf6945b09b44b9a4e5800d403070ae66048997b2197f02181a097e563f9b9acc841139258a258bc610d3bd891637356b2edc8c184c35c65af91aaf7b1c16d74a5f5f862548139254ecf550631d5f8849afdb5b64cf366ff2633a93f3a18c39b5150245fb5f33c9e4e2d94af6963a70b88f9e7e519f8fa2a0f2e3749de883d0e6f052a949d0fc7153a8693f6d801d7352eb2f7a465c0e:
|
||||||
|
2770aadd1d123e9547832dfb2a837eba089179ef4f23abc4a53f2a714e423ee23c5fbb07530dd3a20ff35a500e3708926310fed8a899690232b42c15bd86e5dc:3c5fbb07530dd3a20ff35a500e3708926310fed8a899690232b42c15bd86e5dc:a5cc2055eba3cf6f0c6332c1f2ab5854870913b03ff7093bc94f335add44332231d9869f027d82efd5f1227144ab56e3222dc3ddccf062d9c1b0c1024d9b416dfa3ee8a7027923003465e0ffaefb75b9f29dc6bcf213adc5e318fd8ba93a7aa5bfb495de9d7c5e1a196cd3a2d7721f8ba785aa9052a1811c7fcc8f93932765059cab9c9b718945895ef26f3ac048d4cabf91a9e6aa83ac14d43156827837914eb763a23cba53f60f150f4b70203ec1833ff105849457a8da7327661fb23a554164e05fcf0146b10674964be6f6aa0acc94c41ad57180e5180d199bd9102f55d740e81789b15671bbd0670e6de5d97e1ae626d8a0ebc32c8fd9d24737274e47d2dd5941a272e72a598928ad109cde937bf248d57f5d2942983c51e2a89f8f054d5c48dfad8fcf1ffa97f7de6a3a43ca15fc6720efaec69f0836d84223f9776d111ec2bbc69b2dfd58be8ca12c072164b718cd7c246d64:f267715e9a84c7314f2d5869ef4ab8d2149a13f7e8e1c728c423906293b49ce6283454dd1c7b04741df2eabedc4d6ab1397dc95a679df04d2c17d66c79bb7601a5cc2055eba3cf6f0c6332c1f2ab5854870913b03ff7093bc94f335add44332231d9869f027d82efd5f1227144ab56e3222dc3ddccf062d9c1b0c1024d9b416dfa3ee8a7027923003465e0ffaefb75b9f29dc6bcf213adc5e318fd8ba93a7aa5bfb495de9d7c5e1a196cd3a2d7721f8ba785aa9052a1811c7fcc8f93932765059cab9c9b718945895ef26f3ac048d4cabf91a9e6aa83ac14d43156827837914eb763a23cba53f60f150f4b70203ec1833ff105849457a8da7327661fb23a554164e05fcf0146b10674964be6f6aa0acc94c41ad57180e5180d199bd9102f55d740e81789b15671bbd0670e6de5d97e1ae626d8a0ebc32c8fd9d24737274e47d2dd5941a272e72a598928ad109cde937bf248d57f5d2942983c51e2a89f8f054d5c48dfad8fcf1ffa97f7de6a3a43ca15fc6720efaec69f0836d84223f9776d111ec2bbc69b2dfd58be8ca12c072164b718cd7c246d64:
|
||||||
|
4fdab7c1600e70114b11f533242376af7614b4d5da046ac4bedea21d8a361598a25c9a94d6e4ecd95a4bd6805f762eb1c457a8d45d243238b1839cbba8f441cc:a25c9a94d6e4ecd95a4bd6805f762eb1c457a8d45d243238b1839cbba8f441cc:da405890d11a872c119dab5efcbff61e931f38eccca457edc626d3ea29ed4fe3154fafec1444da74343c06ad90ac9d17b511bcb73bb49d90bafb7c7ea800bd58411df1275c3cae71b700a5dab491a4261678587956aa4a219e1ac6dd3fb2cb8c46197218e726dc7ed234526a6b01c0d72cb93ab3f4f38a08e5940b3f61a72ad2789a0532000fac1d2d2e3ad632ac8b62bb3ff5b99d53597bf4d44b19674924df9b3db3d0253f74627ccab30031c85e291c58b5fa9167522a46746fc307036745d4f9817786e5d300e6c5d503125fea01dec3e3fedbf3861ca2627a0518fb2b24e5a7a014178719e9b345f7b249ce3a413280c8deb674f59a25be92a8ab6400c7c52b0728ae34e22b2ec200c1cbaba2ccd8af29249d17af60c36007a722fc80258a7bebab1cdaad7462a8b7588c2f7e27c6d07afcf60117fed11bd6859e75e3b4fcee3981881e95dd116827dd4b369af069d3c8f2676f8a:5075c090cfbeb6b01802af7f4da5aa4f434d5ee2f3530eebb75c85e08621f83edc08aa96693894a4277633ba81e19e9e55af5c495daa5e1a6f8cbb79c01c7207da405890d11a872c119dab5efcbff61e931f38eccca457edc626d3ea29ed4fe3154fafec1444da74343c06ad90ac9d17b511bcb73bb49d90bafb7c7ea800bd58411df1275c3cae71b700a5dab491a4261678587956aa4a219e1ac6dd3fb2cb8c46197218e726dc7ed234526a6b01c0d72cb93ab3f4f38a08e5940b3f61a72ad2789a0532000fac1d2d2e3ad632ac8b62bb3ff5b99d53597bf4d44b19674924df9b3db3d0253f74627ccab30031c85e291c58b5fa9167522a46746fc307036745d4f9817786e5d300e6c5d503125fea01dec3e3fedbf3861ca2627a0518fb2b24e5a7a014178719e9b345f7b249ce3a413280c8deb674f59a25be92a8ab6400c7c52b0728ae34e22b2ec200c1cbaba2ccd8af29249d17af60c36007a722fc80258a7bebab1cdaad7462a8b7588c2f7e27c6d07afcf60117fed11bd6859e75e3b4fcee3981881e95dd116827dd4b369af069d3c8f2676f8a:
|
||||||
|
264504604e70d72dc4474dbb34913e9c0f806dfe18c7879a41762a9e4390ec61eb2b518ce7dc71c91f3665581651fd03af84c46bf1fed2433222353bc7ec511d:eb2b518ce7dc71c91f3665581651fd03af84c46bf1fed2433222353bc7ec511d:901d70e67ed242f2ec1dda813d4c052cfb31fd00cfe5446bf3b93fdb950f952d94ef9c99d1c264a6b13c3554a264beb97ed20e6b5d66ad84db5d8f1de35c496f947a23270954051f8e4dbe0d3ef9ab3003dd47b859356cecb81c50affa68c15dadb5f864d5e1bb4d3bada6f3aba1c83c438d79a94bfb50b43879e9cef08a2bfb22fad943dbf7683779746e31c486f01fd644905048b112ee258042153f46d1c7772a0624bcd6941e9062cfda75dc8712533f4057335c298038cbca29ebdb560a295a88339692808eb3481fd9735ea414f620c143b2133f57bb64e44778a8ca70918202d157426102e1dfc0a8f7b1ae487b74f02792633154dfe74caa1b7088fda22fa8b9bc354c585f1567706e2955493870f54169e0d7691159df43897961d24a852ea970c514948f3b48f71ee586e72ec78db820f253e08db84f6f312c4333bd0b732fe75883507783e9a1fd4fbab8e5870f9bf7ad58aa:eea439a00f7e459b402b835150a779eed171ab971bd1b58dcc7f9386dadd583de8dc69e267121dde41f0f9493d450b16219cdf3c22f09482ce402fe17ca49e08901d70e67ed242f2ec1dda813d4c052cfb31fd00cfe5446bf3b93fdb950f952d94ef9c99d1c264a6b13c3554a264beb97ed20e6b5d66ad84db5d8f1de35c496f947a23270954051f8e4dbe0d3ef9ab3003dd47b859356cecb81c50affa68c15dadb5f864d5e1bb4d3bada6f3aba1c83c438d79a94bfb50b43879e9cef08a2bfb22fad943dbf7683779746e31c486f01fd644905048b112ee258042153f46d1c7772a0624bcd6941e9062cfda75dc8712533f4057335c298038cbca29ebdb560a295a88339692808eb3481fd9735ea414f620c143b2133f57bb64e44778a8ca70918202d157426102e1dfc0a8f7b1ae487b74f02792633154dfe74caa1b7088fda22fa8b9bc354c585f1567706e2955493870f54169e0d7691159df43897961d24a852ea970c514948f3b48f71ee586e72ec78db820f253e08db84f6f312c4333bd0b732fe75883507783e9a1fd4fbab8e5870f9bf7ad58aa:
|
||||||
|
2ca7447a3668b748b1fd3d52d2080d30e34d397bb2846caf8f659ac168788ca5ab331cd40a31d0173c0c8c1c17002532807bf89e3edb6d34c2dd8294632b9fbc:ab331cd40a31d0173c0c8c1c17002532807bf89e3edb6d34c2dd8294632b9fbc:a82bcd9424bffda0f2f5e9eae17835dbe468f61b785aab82934737a91c5f602cb7c617cdffe87cad726a4972e15a7b8ee147f062d2a5a4d89706b571fa8aa2b95981c78abeaaae86203fa2c0e07297406ea8c27111a86dbe1d5a7c3b7ae930904d9890f6d4abebd1412a73ad5feea64acf065d3e63b5cbe20cf20bbd2d8b94f9053ed5f66633482530124446605918de66455e8cf4b101a127233c4e27d5d55bf95bd3195d0340d43531fc75faf8dded5275bf89750de838fd10c31745be4ca41fa871cb0f9b016706a1a7e3c44bb90ac7a8ad51e272389292fd6c98ad7a069e76e3f5f3e0cc770b9e9b35a765d0d93712d7cdabd17e5d01dd8183af4ad9365db0a0fa41381fce60a081df1c5ab0f8c18f95a7a8b582dfff7f149ea579df0623b33b7508f0c663f01e3a2dcd9dfbee51cc615220fdaffdab51bdae42cb9f7fa9e3b7c69cc8ada5ccd642529ba514fdc54fcf2720b8f5d08b95:f93ada15ae9cd2b54f26f86f0c28392aed5eb6b6b44d01a4e33a54e7da37c38e8d53366f73fd85be642e4ec81236d163f0d025e76c8bbdd65d43df49f09c1f01a82bcd9424bffda0f2f5e9eae17835dbe468f61b785aab82934737a91c5f602cb7c617cdffe87cad726a4972e15a7b8ee147f062d2a5a4d89706b571fa8aa2b95981c78abeaaae86203fa2c0e07297406ea8c27111a86dbe1d5a7c3b7ae930904d9890f6d4abebd1412a73ad5feea64acf065d3e63b5cbe20cf20bbd2d8b94f9053ed5f66633482530124446605918de66455e8cf4b101a127233c4e27d5d55bf95bd3195d0340d43531fc75faf8dded5275bf89750de838fd10c31745be4ca41fa871cb0f9b016706a1a7e3c44bb90ac7a8ad51e272389292fd6c98ad7a069e76e3f5f3e0cc770b9e9b35a765d0d93712d7cdabd17e5d01dd8183af4ad9365db0a0fa41381fce60a081df1c5ab0f8c18f95a7a8b582dfff7f149ea579df0623b33b7508f0c663f01e3a2dcd9dfbee51cc615220fdaffdab51bdae42cb9f7fa9e3b7c69cc8ada5ccd642529ba514fdc54fcf2720b8f5d08b95:
|
||||||
|
494ea9bcce26885b7d17d1fc114448f239f0ce46e5f247b4c999fa86296924726901e5efae57536ba5fdd96b59657359065f25d391a1aa8cdc0d38bb5d53c139:6901e5efae57536ba5fdd96b59657359065f25d391a1aa8cdc0d38bb5d53c139:3badbfa5f5a8aa2cce0a60e686cdce654d24452f98fd54872e7395b39464380a0e185557ea134d095730864f4254d3dd946970c10c804fcc0899dfa024205be0f80b1c75449523324fe6a0751e47b4ff4822b8c33e9eaf1d1d96e0de3d4acd89696b7fcc03d49f92f82b9725700b350db1a87615369545561b8599f5ea920a310a8bafc0e8d7468cbf6f3820e943594afdd5166e4e3309dddd7694ef67e694f34fc62724ff96ac3364176f34e8a02b4cf569db5b8f77d58512aedabf0bcd1c2df12db3a9473f948c5c3243309aae46c49efd088b60f31a8a72ad7e5a35acc5d89fa66807eb5d3ba9cdf08d4753cb85089ee36f5c96b432b6928352afad58012225d6157f9e3611426df921b6d1d8374628a63031e9ffb90e42ffbba021f174f68503155430152c9155dc98ffa26c4fab065e1f8e4622c2f28a8cb043110b617441140f8e20adc16f799d1d5096b1f50532be5042d21b81ea46c7:548a093a680361b7dc56f14503b55eeec3b3f4fd4ca99d6aedce0830f7f4ae2f7328539b34c48fc9760922333dae9c7c017e7db73b8faa6c06be05e347992b063badbfa5f5a8aa2cce0a60e686cdce654d24452f98fd54872e7395b39464380a0e185557ea134d095730864f4254d3dd946970c10c804fcc0899dfa024205be0f80b1c75449523324fe6a0751e47b4ff4822b8c33e9eaf1d1d96e0de3d4acd89696b7fcc03d49f92f82b9725700b350db1a87615369545561b8599f5ea920a310a8bafc0e8d7468cbf6f3820e943594afdd5166e4e3309dddd7694ef67e694f34fc62724ff96ac3364176f34e8a02b4cf569db5b8f77d58512aedabf0bcd1c2df12db3a9473f948c5c3243309aae46c49efd088b60f31a8a72ad7e5a35acc5d89fa66807eb5d3ba9cdf08d4753cb85089ee36f5c96b432b6928352afad58012225d6157f9e3611426df921b6d1d8374628a63031e9ffb90e42ffbba021f174f68503155430152c9155dc98ffa26c4fab065e1f8e4622c2f28a8cb043110b617441140f8e20adc16f799d1d5096b1f50532be5042d21b81ea46c7:
|
||||||
|
00d735ebaee75dd579a40dfd82508274d01a1572df99b811d5b01190d82192e4ba02517c0fdd3e2614b3f7bf99ed9b492b80edf0495d230f881730ea45bc17c4:ba02517c0fdd3e2614b3f7bf99ed9b492b80edf0495d230f881730ea45bc17c4:59c0b69af95d074c88fdc8f063bfdc31b5f4a9bc9cecdffa8128e01e7c1937dde5eb0570b51b7b5d0a67a3555b4cdce2bca7a31a4fe8e1d03ab32b4035e6dadbf1532059ee01d3d9a7633a0e706a1154cab22a07cd74c06a3cb601244cf3cf35a35c3100ba47f31372a2da65dcff0d7a80a1055d8aa99212e899aad7f02e949e6fee4d3c9cefa85069eaff1f6ad06fc300c871ab82b2bedb934d20875c2a263242cdb7f9be192a8710b24c7ea98d43daec8baa5553c678a38f0e0adf7d3ff2dcc799a1dbad6eab1c3d9458a9db922f02e75cfab9d65c7336dae71895d5bb15cac203f2b38b9996c410f8655ad22d3c091c20b7f926d45e780128f19747462abc5c58932fbb9e0bc62d53868802f1b083f183b8a1f9434986d5cf97c04e2f3e145730cba98779c7fed0cab1c05d5e4653c6c3f6736260bc78ee4372862ffe9e90371d762c7432781f35ced884a4baca05653ef25f25a6f3d5628308:dcdc54611937d2bd06cacd9818b3be15ce7425427a75f50d197a337a3b8ba6714ef48866f243bd5ac7415e914517a2c1c5a953f432b99db0e620d64f74eb850559c0b69af95d074c88fdc8f063bfdc31b5f4a9bc9cecdffa8128e01e7c1937dde5eb0570b51b7b5d0a67a3555b4cdce2bca7a31a4fe8e1d03ab32b4035e6dadbf1532059ee01d3d9a7633a0e706a1154cab22a07cd74c06a3cb601244cf3cf35a35c3100ba47f31372a2da65dcff0d7a80a1055d8aa99212e899aad7f02e949e6fee4d3c9cefa85069eaff1f6ad06fc300c871ab82b2bedb934d20875c2a263242cdb7f9be192a8710b24c7ea98d43daec8baa5553c678a38f0e0adf7d3ff2dcc799a1dbad6eab1c3d9458a9db922f02e75cfab9d65c7336dae71895d5bb15cac203f2b38b9996c410f8655ad22d3c091c20b7f926d45e780128f19747462abc5c58932fbb9e0bc62d53868802f1b083f183b8a1f9434986d5cf97c04e2f3e145730cba98779c7fed0cab1c05d5e4653c6c3f6736260bc78ee4372862ffe9e90371d762c7432781f35ced884a4baca05653ef25f25a6f3d5628308:
|
||||||
|
8c34b905440b61911d1d8137c53d46a1a76d4609af973e18eb4c5709295627bbb69a8b2fdf5c20e734c2ffb294bc8ae1011d664f11afe7fbc471925cf72fa99d:b69a8b2fdf5c20e734c2ffb294bc8ae1011d664f11afe7fbc471925cf72fa99d:30b57a389b48a0beb1a48432bff6b314bded79c4a1763a5acb57cea1bfb4c6d016cf090f5bd05bbd114e33ae7c17782dfa264f46c45f8c599c603016fe9ff05b6b5a99e92fe713a4cd5c41b292ed2bb2e9cf33a440542e821ec82cbf665c3f02e3dc337d7fdb58e31b27cb2954541468814698510df18c85c81fad12db11ec6b966f4930da5646b991db97445097da30dab61cda53a41083cb96add19de6c5eec323bca9d3530e38c00b35af7360077601be6ac97f3030f930a27b90fe8b6911bae389065adc15e1882300e2a003274d23182d5efd5ba4b9130c07bd5c65fecb8b5cb7eb38836b318befdfd77de4d6ca0181f77ae5740891683225f549dd8426145c97c5818c319f7ab2d868e1a41ceab64c085116069897bf2ca3667652406155ed0646431b6de1ccc03b4279ae4d326679265dce82048e7298e1f87fcec0768ac0f5d8ff84f7210be54d411af8edea7217f4e59413121e148c60da:3e0b72073dc9375eedcca6c4fc1cd315938a050c92716bd2284f4629a962beec0b7d7cf16ab923d58f5b90d3901a8e5c75c8f17dab9998e007d8c49511973d0e30b57a389b48a0beb1a48432bff6b314bded79c4a1763a5acb57cea1bfb4c6d016cf090f5bd05bbd114e33ae7c17782dfa264f46c45f8c599c603016fe9ff05b6b5a99e92fe713a4cd5c41b292ed2bb2e9cf33a440542e821ec82cbf665c3f02e3dc337d7fdb58e31b27cb2954541468814698510df18c85c81fad12db11ec6b966f4930da5646b991db97445097da30dab61cda53a41083cb96add19de6c5eec323bca9d3530e38c00b35af7360077601be6ac97f3030f930a27b90fe8b6911bae389065adc15e1882300e2a003274d23182d5efd5ba4b9130c07bd5c65fecb8b5cb7eb38836b318befdfd77de4d6ca0181f77ae5740891683225f549dd8426145c97c5818c319f7ab2d868e1a41ceab64c085116069897bf2ca3667652406155ed0646431b6de1ccc03b4279ae4d326679265dce82048e7298e1f87fcec0768ac0f5d8ff84f7210be54d411af8edea7217f4e59413121e148c60da:
|
||||||
|
77a83e18c9f000eeff7deeac959ecba2206c0aa39d2f0e2aed5729482a7a022962b1b316135596bfbca6037ed847c61fb7f09fa36ce90abb7789b86f768b59dd:62b1b316135596bfbca6037ed847c61fb7f09fa36ce90abb7789b86f768b59dd:f3d5fa2acaefd858f1df26e03059cdcbc2468ad74afc993d0db9c4cde4113f8d55c7da71d38ba06520531c61fddb5f33d5f0353be2376e580711be45c0a30b1fa01b55e228c6fa35e3f95b67909fc7df3fd464d93d661a926f9d11f7550c17fbcc3496526e8f10e0c8916677b2be5b319b688f21e81aaa9482e5c93e64ce8c437b9c1e14fefed70a3fee568811dc31cadab3d5b220254465336dc4d97a3bd096b5e065e0cfbe82849e2c1905aca486533f0da7a61f1e9a55b8e2a83262deeb59f2b13d3a8aef5700845b83b25ae2183c0ddac0ce42f8d25674cb0d0d220a6de7c1858bb07d59a3372344d944602aa451d2b937db0fe6feca0beba81721fc361ea7509e2b6d397e1c191b56f54ab436d0d27ab4c061bd661ad1a4452387e8735754d07fa7ef4d4548b172582425b299046e6301b5ba6b914418f149cf722e10bde2e0d41700f12c8429fc897b7819da92292240cd45565458c9a7b29c12:1eaad8420ac12c99ac1ff4476678e3cbbe94da6a797f174664d5ee0f641433fb1e7cb2f5613e10805df8654cd8e0d45d96230932bc7f20b04eae836435134309f3d5fa2acaefd858f1df26e03059cdcbc2468ad74afc993d0db9c4cde4113f8d55c7da71d38ba06520531c61fddb5f33d5f0353be2376e580711be45c0a30b1fa01b55e228c6fa35e3f95b67909fc7df3fd464d93d661a926f9d11f7550c17fbcc3496526e8f10e0c8916677b2be5b319b688f21e81aaa9482e5c93e64ce8c437b9c1e14fefed70a3fee568811dc31cadab3d5b220254465336dc4d97a3bd096b5e065e0cfbe82849e2c1905aca486533f0da7a61f1e9a55b8e2a83262deeb59f2b13d3a8aef5700845b83b25ae2183c0ddac0ce42f8d25674cb0d0d220a6de7c1858bb07d59a3372344d944602aa451d2b937db0fe6feca0beba81721fc361ea7509e2b6d397e1c191b56f54ab436d0d27ab4c061bd661ad1a4452387e8735754d07fa7ef4d4548b172582425b299046e6301b5ba6b914418f149cf722e10bde2e0d41700f12c8429fc897b7819da92292240cd45565458c9a7b29c12:
|
||||||
|
73b03373ef1fd849005ecd6270dd9906f19f4439e40376cdbc520902bc976812663719e08ba3ba1666f6069a3f54991866b18cc6be41991b02eb3026ff9e155f:663719e08ba3ba1666f6069a3f54991866b18cc6be41991b02eb3026ff9e155f:d5c2deaba795c30aba321bc7de6996f0d90e4d05c747fb4dae8f3451895def6e16e72f38eace756f36635f8fb0b72a3a0c1f54663817a94d4fd346f835ab0e657f001a6f2cecb86d0825bd02639254f7f7f38ca99dbb86c64a633f73baf933aae3563281f4005e2d0e7cec9fbde8e588a957e211068be65b3d3d35bf4e8d5bb3478333df9ced9b2abaf48697994a145e9321499fc5ee560f4fbb6849e1ae8eb3d1de0083a21a03f6a6b28176f0130d3895e50e75e3d7d0947a7bc2c5b9ff69895d27791442ba8d0f2180712b567f712ea912f3b0d92c19342e0106ff1d87b46ad33af300b90855ba9769d366e79425d98e4de19905a04577707cbe625b84691781cd26bf62260b4a8bd605f77af6f970e1b3a112e8918344bd0d8d2e41dfd2ce9895b0246e50887aa3a577ff73be4b6ae60feb0ca36f6a5f8171ed209e5c566529c0940d9b4bd744ccee56e54a9a0c6e4da520dd315c2872b02db563703e:a40abe98fc69da8a1ff9ff5c2cca93632e975980ee8b82c3c376022d6524ab736d01b072f2b681b5f1cd3ea067012ed6d074e949c42327a366caa9e4750a3c08d5c2deaba795c30aba321bc7de6996f0d90e4d05c747fb4dae8f3451895def6e16e72f38eace756f36635f8fb0b72a3a0c1f54663817a94d4fd346f835ab0e657f001a6f2cecb86d0825bd02639254f7f7f38ca99dbb86c64a633f73baf933aae3563281f4005e2d0e7cec9fbde8e588a957e211068be65b3d3d35bf4e8d5bb3478333df9ced9b2abaf48697994a145e9321499fc5ee560f4fbb6849e1ae8eb3d1de0083a21a03f6a6b28176f0130d3895e50e75e3d7d0947a7bc2c5b9ff69895d27791442ba8d0f2180712b567f712ea912f3b0d92c19342e0106ff1d87b46ad33af300b90855ba9769d366e79425d98e4de19905a04577707cbe625b84691781cd26bf62260b4a8bd605f77af6f970e1b3a112e8918344bd0d8d2e41dfd2ce9895b0246e50887aa3a577ff73be4b6ae60feb0ca36f6a5f8171ed209e5c566529c0940d9b4bd744ccee56e54a9a0c6e4da520dd315c2872b02db563703e:
|
||||||
|
eab179e41ed5c889ffe6aabdc054faf1307c395e46e313e17a14fe01023ffa3086f34746d3f7a01ddbe322f1aca56d22856d38733a3a6900bb08e776450ec803:86f34746d3f7a01ddbe322f1aca56d22856d38733a3a6900bb08e776450ec803:971095cebe5031530224387c5c31966e389b8566390054cf45264b44e18964b7be52c33c4ffb259af16283438fa15dd66bc7791b7533ef10cb0beab524a6437626f4cc74512851adcc2fb129055a482c61107383fb7c5241831d5551634eef0dc0b8f9053a00971aa8fa1ae0898e4b481b6707e97c0f942040b339d92fc17bbade74675af243d8b2dafb15b1db55d12415b85f3037291930ab61600ba3431f8eb425be4491614728af101e81c091f348bc5ffd1bde6ae6cad5c15b3aa7358078cc4effb54a86e7f0e0c55e4cfe0a54605ed443fdf2aaba016585da617e77341d52889d75dd540d39fe8b7993ed705cfddea0cb0d5a731d6bfcdb816afaff47e963eedebdf241af5593353d6d401a34f029a8cdeb1904cc2caa4f9635cc2ba6b7b1a29da625ffc383be2f5a8f1fa4f39b2d4b4f4c2d8838ce258a04d4a120493fdf07f68c0ffd1c16b768a35c55fea2cac696b5c20efc10865cde8a64627dcd:143cb28027c2f82e375e5f340e7fe6e60ce7bd51000b49c74168af85e26ed2ed630ed2672090164cc54b052da694ebdd21a21b3053f4dcfd7895ea5f6c8aa80d971095cebe5031530224387c5c31966e389b8566390054cf45264b44e18964b7be52c33c4ffb259af16283438fa15dd66bc7791b7533ef10cb0beab524a6437626f4cc74512851adcc2fb129055a482c61107383fb7c5241831d5551634eef0dc0b8f9053a00971aa8fa1ae0898e4b481b6707e97c0f942040b339d92fc17bbade74675af243d8b2dafb15b1db55d12415b85f3037291930ab61600ba3431f8eb425be4491614728af101e81c091f348bc5ffd1bde6ae6cad5c15b3aa7358078cc4effb54a86e7f0e0c55e4cfe0a54605ed443fdf2aaba016585da617e77341d52889d75dd540d39fe8b7993ed705cfddea0cb0d5a731d6bfcdb816afaff47e963eedebdf241af5593353d6d401a34f029a8cdeb1904cc2caa4f9635cc2ba6b7b1a29da625ffc383be2f5a8f1fa4f39b2d4b4f4c2d8838ce258a04d4a120493fdf07f68c0ffd1c16b768a35c55fea2cac696b5c20efc10865cde8a64627dcd:
|
||||||
|
fbf146ebd51075570ec51ac410ae9f391db75b610ada6362b4dbd949656cfb66be7c2f5b21d746c8ea3245ce6f268e9da74e00fa85c9c475260c68fa1af6361f:be7c2f5b21d746c8ea3245ce6f268e9da74e00fa85c9c475260c68fa1af6361f:cd7ad4f17fcff73acc402dc102d09079b29aaf2a0f4b27cf6beeb1e2b23d19ab47deb3ae1becd68861ea279c46691738f4fff47c43047c4f8b56b6bbcc3fde0723d44120dcd307a6310dc4f366b8f3cd52db19b8266a487f7872391c45fe0d3248a7abf2c20022d3769547f683067dcc363cd22fd7cda3cadc15804056f0e2aa2b795008c598be7a961805e6df291ba3041c47ff5640275f46e6ae82092d21abcbcfba11e730216008822de3ce462400596da79f7ae5d1df8389112ad98868fa94fb0546bfe6a67aa8d28c4d32072d2eadd6256255f18c2382e662dfa922a680e06a43622c4871d27d1807f7b2703070c83db8dd929c06038b2183cb8e2b9ec4c778d7ecf9e9ffac77fa7737b055feac2e7982aeeec0b72f1bbca2424e1a844bbac79cb2e7400f81dc449d0560b521a7c16bb4167e6696586058a9b8ed2e5116690b77f2a17e5c0b16a83dcbd2e24552293e258b32ba7f844944379342698627:6768006fe0f201b217dd10eb05d4b82adcfeb2ecfc8373c3308f4150394811eb60491881a2e53d1289d96478e18a64c34b2a19832cdccfd96a2e4a0c469fdc0bcd7ad4f17fcff73acc402dc102d09079b29aaf2a0f4b27cf6beeb1e2b23d19ab47deb3ae1becd68861ea279c46691738f4fff47c43047c4f8b56b6bbcc3fde0723d44120dcd307a6310dc4f366b8f3cd52db19b8266a487f7872391c45fe0d3248a7abf2c20022d3769547f683067dcc363cd22fd7cda3cadc15804056f0e2aa2b795008c598be7a961805e6df291ba3041c47ff5640275f46e6ae82092d21abcbcfba11e730216008822de3ce462400596da79f7ae5d1df8389112ad98868fa94fb0546bfe6a67aa8d28c4d32072d2eadd6256255f18c2382e662dfa922a680e06a43622c4871d27d1807f7b2703070c83db8dd929c06038b2183cb8e2b9ec4c778d7ecf9e9ffac77fa7737b055feac2e7982aeeec0b72f1bbca2424e1a844bbac79cb2e7400f81dc449d0560b521a7c16bb4167e6696586058a9b8ed2e5116690b77f2a17e5c0b16a83dcbd2e24552293e258b32ba7f844944379342698627:
|
||||||
|
dff0eb6b426dea2fd33c1d3fc24df9b31b486facb7edb8502954a3e8da99d9fdc245085ece69fb9aa560d0c27fdb634f7a840d41d8463660fbe82483b0f3cc3a:c245085ece69fb9aa560d0c27fdb634f7a840d41d8463660fbe82483b0f3cc3a:e7c9e313d86160f4c74aa0ae07369ee22b27f81b3f69097affae28dae48483fb52a5c062306b59610f5cdbff6332b1960cd6f2b8f7b41578c20f0bc9637a0fdfc739d61f699a573f1c1a0b49294506cf4487965e5bb07bbf81803cb3d5cb3829c66c4bee7fc800ede216150934d277dea50edb097b992f11bb669fdf140bf6ae9fec46c3ea32f888fde9d154ea84f01c51265a7d3fef6eefc1ccdbffd1e2c897f05546a3b1ca11d9517cd667c660ec3960f7a8e5e80202a78d3a388b92f5c1dee14ae6acf8e17c841c9557c35a2eeced6e6af6372148e483ccd06c8fe344924e1019fb91cbf7941b9a176a073415867210670410c5dbd0ac4a50e6c0a509ddfdc555f60d696d41c77db8e6c84d5181f872755e64a721b061fcd68c463db4d32c9e01ea501267de22879d7fc12c8ca0379edb45abaa6e64dda2af6d40ccf24fbebad7b5a8d3e52007945ecd3ddc1e3efeb522581ac80e98c863ba0c590a3ed95cd1:6b48b10f545ddb7a89cd5829f4e5b20146cf6bc96e550d06f65de8bdae7ccdded26cd630f86c9266bccf88e924033e04f83a54f8290d7f734cf8673cca8f9703e7c9e313d86160f4c74aa0ae07369ee22b27f81b3f69097affae28dae48483fb52a5c062306b59610f5cdbff6332b1960cd6f2b8f7b41578c20f0bc9637a0fdfc739d61f699a573f1c1a0b49294506cf4487965e5bb07bbf81803cb3d5cb3829c66c4bee7fc800ede216150934d277dea50edb097b992f11bb669fdf140bf6ae9fec46c3ea32f888fde9d154ea84f01c51265a7d3fef6eefc1ccdbffd1e2c897f05546a3b1ca11d9517cd667c660ec3960f7a8e5e80202a78d3a388b92f5c1dee14ae6acf8e17c841c9557c35a2eeced6e6af6372148e483ccd06c8fe344924e1019fb91cbf7941b9a176a073415867210670410c5dbd0ac4a50e6c0a509ddfdc555f60d696d41c77db8e6c84d5181f872755e64a721b061fcd68c463db4d32c9e01ea501267de22879d7fc12c8ca0379edb45abaa6e64dda2af6d40ccf24fbebad7b5a8d3e52007945ecd3ddc1e3efeb522581ac80e98c863ba0c590a3ed95cd1:
|
||||||
|
9f32958c7679b90fd5036056a75ec2eb2f56ec1effc7c012461dc89a3a1674201d7269dcb6d1f584e662d4ce251de0aba290ef78b97d448afb1e5333f1976d26:1d7269dcb6d1f584e662d4ce251de0aba290ef78b97d448afb1e5333f1976d26:a56ba86c71360504087e745c41627092ad6b49a71e9daa5640e1044bf04d4f071ad728779e95d1e2460584e6f0773545da82d4814c9189a120f12f3e3819813e5b240d0f26436f70ee353b4d20cea54a1460b5b8f1008d6f95f3aa2d8f1e908fced50d624e3a096938b9353854b96da463a2798a5a312ec790842c10c446e3350c764bf5c972593b9987bf23256daa8894d47f22e85b97607e66fc08a12c789c4746080368d321bb9015a1155b65523ad8e99bb989b44eac756b0734acd7c6357c70b59743246d1652d91b0f9896965141345b9945cf34980452f3502974edb76b9c785fb0f4395266b055f3b5db8aab68e9d7102a1cd9ee3d142504f0e88b282e603a738e051d98de05d1fcc65b5f7e99c4111cc0aec489abd0ecad311bfc13e7d1653b9c31e81c998037f959d5cd980835aa0e0b09bcbed634391151da02bc01a36c9a5800afb984163a7bb815edbc0226eda0595c724ca9b3f8a71178f0d20a5a:9881a5763bdb259a3fefbba3d957162d6c70b804fa94ab613406a6ec42505b8789465ca1a9a33e1895988842270c55e5bdd5483f6b17b31781b593507a6c1808a56ba86c71360504087e745c41627092ad6b49a71e9daa5640e1044bf04d4f071ad728779e95d1e2460584e6f0773545da82d4814c9189a120f12f3e3819813e5b240d0f26436f70ee353b4d20cea54a1460b5b8f1008d6f95f3aa2d8f1e908fced50d624e3a096938b9353854b96da463a2798a5a312ec790842c10c446e3350c764bf5c972593b9987bf23256daa8894d47f22e85b97607e66fc08a12c789c4746080368d321bb9015a1155b65523ad8e99bb989b44eac756b0734acd7c6357c70b59743246d1652d91b0f9896965141345b9945cf34980452f3502974edb76b9c785fb0f4395266b055f3b5db8aab68e9d7102a1cd9ee3d142504f0e88b282e603a738e051d98de05d1fcc65b5f7e99c4111cc0aec489abd0ecad311bfc13e7d1653b9c31e81c998037f959d5cd980835aa0e0b09bcbed634391151da02bc01a36c9a5800afb984163a7bb815edbc0226eda0595c724ca9b3f8a71178f0d20a5a:
|
||||||
|
f86d6f766f88b00717b7d6327eb26cf3ceeba5385184426f9cfd8295e2421ff2cb1d250504754183704dbe21c323d66f9f9011758f6d8dab6f597b199662145b:cb1d250504754183704dbe21c323d66f9f9011758f6d8dab6f597b199662145b:da8423a6b7a18f20aa1f90ed2331b17b24067c40175bc25d8109e21d87ac00528eb3b2f66a2b52dc7ef2f8cecb75c76099cfa23db8da897043ba1cce31e2dfea46075f5e073203eaeb3d62c84c107b6dab33a14eaf149aa61850c15f5a58d88a15aba9196f9e495e8dbecbcf7e8444f5dd72a08a099d7f6209990b562974ea829ef11d29a920e3a799d0d92cb50d50f817631ab09de97c31e9a05f4d78d649fcd93a83752078ab3bb0e16c564d4fb07ca923c0374ba5bf1eea7e73668e135031feafcbb47cbc2ae30ec16a39b9c337e0a62eecdd80c0b7a04924ac3972da4fa9299c14b5a53d37b08bf02268b3bac9ea9355090eeb04ad87bee0593ba4e4443dda38a97afbf2db9952df63f178f3b4c52bcc132be8d9e26881213abdeb7e1c44c4061548909f0520f0dd7520fc408ea28c2cebc0f53063a2d30570e05350e52b390dd9b67662984847be9ad9b4cd50b069ffd29dd9c62ef14701f8d012a4a70c8431cc:ec61c0b292203a8f1d87235ede92b74723c8d23408423773ae50b1e9bc4464e03e446da9dce4c39f6dd159bea26c009ed00120bc36d4a247dc0d24bcefcc110cda8423a6b7a18f20aa1f90ed2331b17b24067c40175bc25d8109e21d87ac00528eb3b2f66a2b52dc7ef2f8cecb75c76099cfa23db8da897043ba1cce31e2dfea46075f5e073203eaeb3d62c84c107b6dab33a14eaf149aa61850c15f5a58d88a15aba9196f9e495e8dbecbcf7e8444f5dd72a08a099d7f6209990b562974ea829ef11d29a920e3a799d0d92cb50d50f817631ab09de97c31e9a05f4d78d649fcd93a83752078ab3bb0e16c564d4fb07ca923c0374ba5bf1eea7e73668e135031feafcbb47cbc2ae30ec16a39b9c337e0a62eecdd80c0b7a04924ac3972da4fa9299c14b5a53d37b08bf02268b3bac9ea9355090eeb04ad87bee0593ba4e4443dda38a97afbf2db9952df63f178f3b4c52bcc132be8d9e26881213abdeb7e1c44c4061548909f0520f0dd7520fc408ea28c2cebc0f53063a2d30570e05350e52b390dd9b67662984847be9ad9b4cd50b069ffd29dd9c62ef14701f8d012a4a70c8431cc:
|
||||||
|
a5b34cefab9479df8389d7e6f6c146aa8affb0bec837f78af64624a145cc344e7b0f4f24d9972bc6fe83826c52716ad1e0d7d19f123858cb3e99fa636ac9631a:7b0f4f24d9972bc6fe83826c52716ad1e0d7d19f123858cb3e99fa636ac9631a:e21e98af6c2bac70557eb0e864da2c2b4d6c0a39a059d3477251f6178a39676f4749e7fbea623f148a43a8b0fe0610506fa658abd2f5fa39198f2636b724db22d1aebc2ab07b2b6dbffdee8cece81e1af1493ec1964e16bf86ab258ca0feb77e3c8717e44038abe152c14be15660bf93b2d48d92c4ed7074d2494210621bcf204fba88c654d5ffe01e1a53d08f70bb237089dc807216ff6a85dbec3102237d42590778acf6c1dc566d5a2bb9a63bc21c329c272e5965baeeb0fe891de3cc8cbfa8e541a8881df68942e7ff8dc656bd08575f6aaf924a176d663b1a1f43574d11768c701b269561e55438dbebfd443d2115cb933d1cde4a915b54c325c27f499ef02bd012ff1f9a36390922887600fe712bcdc23eb5974a305372ad52951f83f0e58cc49e289841621917f1fcb0235147240dae4cf3b99b6ac6d8de94efe7c4436714508bcd0114c56068ff1b7c16d51bd906437874d6549ab5d8087896872ec8a09d7412:2fbd899d72b6d39e4f45b8b62cbbd5f3c0acb1ad8540913fa585877e91ccfef7bee50a4b0f9fedf5cc1e0d1953ad399c8389a93391e1b7c929af6d6f3b796c08e21e98af6c2bac70557eb0e864da2c2b4d6c0a39a059d3477251f6178a39676f4749e7fbea623f148a43a8b0fe0610506fa658abd2f5fa39198f2636b724db22d1aebc2ab07b2b6dbffdee8cece81e1af1493ec1964e16bf86ab258ca0feb77e3c8717e44038abe152c14be15660bf93b2d48d92c4ed7074d2494210621bcf204fba88c654d5ffe01e1a53d08f70bb237089dc807216ff6a85dbec3102237d42590778acf6c1dc566d5a2bb9a63bc21c329c272e5965baeeb0fe891de3cc8cbfa8e541a8881df68942e7ff8dc656bd08575f6aaf924a176d663b1a1f43574d11768c701b269561e55438dbebfd443d2115cb933d1cde4a915b54c325c27f499ef02bd012ff1f9a36390922887600fe712bcdc23eb5974a305372ad52951f83f0e58cc49e289841621917f1fcb0235147240dae4cf3b99b6ac6d8de94efe7c4436714508bcd0114c56068ff1b7c16d51bd906437874d6549ab5d8087896872ec8a09d7412:
|
||||||
|
ad75c9ce299c4d59393367d77a4c9f8df8dcec765c6dbd25b527fb7669913604b9910548fe6312a119c9993eebcfb9dc90030ffb0e4de2b7ccd23cbeb4fef71b:b9910548fe6312a119c9993eebcfb9dc90030ffb0e4de2b7ccd23cbeb4fef71b:62fc5ab67deb1fee9ab6cca3b88a1df1e589f0fd4a88f4aa7738948761fe84372c5b18e4655220c1d84d52acad32e229a5c756c20fc62fe4b4b4e5fd7077ae4ed5397aa796f2307ceedb6505b39297856f4aeb5e70938e36ee24a0ac7d9868306f6b53910623b7dc89a6672ad738576ed5d88831dd338321c8902bc2061f65e94d452fdfa0dc665cefb92308e52301bd4627006b363d06b775a395914d8c863e95a00d6893f3376134c429f56478145e4456f7a12d65bb2b8965d728cb2ddbb708f7125c237095a92195d92fa727a372f3545ae701f3808fee802c8967a76e8a940e55fb2d810bfb47ada156f0eda1829b159cf05c7f36cf3847d7b21de84c3dc0fe658347f79396a01139a508b60022db1c0e5aeef47e445e66f783e62c96597bdb16f209c08a9132c7573136170ee3ebf24261265a89fb4f10333375e20b33ab7403464f5249461c6853c5fddb9f58af816892910393a7077b799fdc3489720998feea86:6b7ef27bcfbf2b714985033764fccff555e3f5bc44610d6c8c62117cb3831a07f4a8bddb0eaed1d46b0289b15de1aa4dcc17d71be96a09e66ba4dc4627c7870562fc5ab67deb1fee9ab6cca3b88a1df1e589f0fd4a88f4aa7738948761fe84372c5b18e4655220c1d84d52acad32e229a5c756c20fc62fe4b4b4e5fd7077ae4ed5397aa796f2307ceedb6505b39297856f4aeb5e70938e36ee24a0ac7d9868306f6b53910623b7dc89a6672ad738576ed5d88831dd338321c8902bc2061f65e94d452fdfa0dc665cefb92308e52301bd4627006b363d06b775a395914d8c863e95a00d6893f3376134c429f56478145e4456f7a12d65bb2b8965d728cb2ddbb708f7125c237095a92195d92fa727a372f3545ae701f3808fee802c8967a76e8a940e55fb2d810bfb47ada156f0eda1829b159cf05c7f36cf3847d7b21de84c3dc0fe658347f79396a01139a508b60022db1c0e5aeef47e445e66f783e62c96597bdb16f209c08a9132c7573136170ee3ebf24261265a89fb4f10333375e20b33ab7403464f5249461c6853c5fddb9f58af816892910393a7077b799fdc3489720998feea86:
|
||||||
|
1ced574529b9b416977e92eb39448a8717cac2934a243a5c44fb44b73ccc16da85e167d5f062fee82014f3c8b1beaed8eefb2c22d8649c424b86b21b11eb8bda:85e167d5f062fee82014f3c8b1beaed8eefb2c22d8649c424b86b21b11eb8bda:1b3b953cce6d15303c61ca707609f70e7250f6c0deba56a8ce522b5986689651cdb848b842b2229661b8eeabfb8570749ed6c2b10a8fbf515053b5ea7d7a9228349e4646f9505e198029fec9ce0f38e4e0ca73625842d64caf8ced070a6e29c743586aa3db6d82993ac71fd38b783162d8fe04ffd0fa5cbc381d0e219c91937df6c973912fc02fda5377312468274c4bee6dca7f79c8b544861ed5babcf5c50e1473491be01708ac7c9ff58f1e40f855497ce9d7cc47b9410f2edd00f6496740243b8d03b2f5fa742b9c630867f77ac42f2b62c14e5ebddc7b647a05fff43670745f2851eff4909f5d27d57ae87f61e965ee60fdf97724c59267f2610b7ad5de919856d64d7c212659ce8656149b6a6d29d8f92b312be50b6e2a431d36ae022b00a6fe360e3af65432899c43be0427e36d21cfec81f21aa53b33db5ed2c37da8f96ac3e7dc67a1de37546cf7de1008c7e1adbe0f34fa7eb2434d94e6a13f4cf86a98d497622f:e0303aefe08a77738dcc657afbb9b835ed279613a53c73fdc5ddbfb350e5cff4d6c9bb43dc07c95bf4e23b64c40f8804c7169952e3c8d59a7197241bfed0740f1b3b953cce6d15303c61ca707609f70e7250f6c0deba56a8ce522b5986689651cdb848b842b2229661b8eeabfb8570749ed6c2b10a8fbf515053b5ea7d7a9228349e4646f9505e198029fec9ce0f38e4e0ca73625842d64caf8ced070a6e29c743586aa3db6d82993ac71fd38b783162d8fe04ffd0fa5cbc381d0e219c91937df6c973912fc02fda5377312468274c4bee6dca7f79c8b544861ed5babcf5c50e1473491be01708ac7c9ff58f1e40f855497ce9d7cc47b9410f2edd00f6496740243b8d03b2f5fa742b9c630867f77ac42f2b62c14e5ebddc7b647a05fff43670745f2851eff4909f5d27d57ae87f61e965ee60fdf97724c59267f2610b7ad5de919856d64d7c212659ce8656149b6a6d29d8f92b312be50b6e2a431d36ae022b00a6fe360e3af65432899c43be0427e36d21cfec81f21aa53b33db5ed2c37da8f96ac3e7dc67a1de37546cf7de1008c7e1adbe0f34fa7eb2434d94e6a13f4cf86a98d497622f:
|
||||||
|
f0790d93e2d3b84f61ef4c807147aba410e415e72b71b0d61d01026fed99da3defdf649fb033cf328e0b287796f8a25e9c6e2e871b33c2c21a4028a8a25a4b28:efdf649fb033cf328e0b287796f8a25e9c6e2e871b33c2c21a4028a8a25a4b28:7973e9f32d74805992eb65da0d637335e50eff0ce68ea2d1f3a02de704492b9cfbe7e7ba96fdb42bb821a513d73fc60402e92c855deaed73ffeaf70952029062c833e14ec1b14f144e2207f6a0e727e5a7e3cbab27d5972970f69518a15b093e740cc0ce11bf5248f0826b8a98bde8bf2c7082c97aff158d08371118c89021cc3974ae8f76d86673c3f824b62c79c4b41f40eaa8943738f03300f68cbe175468eb235a9ff0e6537f8714e97e8f08ca444e41191063b5fabd156e85dcf66606b81dad4a95065584b3e0658c20a706eaf4a0777da4d2e0cd2a0fca60109c2b4403db3f03cd4781c1fbb0272202bcb11687808c50cb98f64b7f3fd3d43333bb5a061b9e377090abb1e0a885cb26b73c163e63ff6451ff2f4ec8249c7e152bd03973a1e964e2b5b235281a938399a112a24529e383a560dc50bb1b622ad74ef35658dcb10ffe022568ac3ffae5b465a8ed7643e8561b352ee9944a35d882c712b187788a0abae5a22f:08773a6a78762cbb1e25fcbb29139941bdf16f4e09a1fa08fc701f32f933edd74c0ae983c12a0a5b020b6bcf44bb719dde8ed0781a8298265640e1608c98b3017973e9f32d74805992eb65da0d637335e50eff0ce68ea2d1f3a02de704492b9cfbe7e7ba96fdb42bb821a513d73fc60402e92c855deaed73ffeaf70952029062c833e14ec1b14f144e2207f6a0e727e5a7e3cbab27d5972970f69518a15b093e740cc0ce11bf5248f0826b8a98bde8bf2c7082c97aff158d08371118c89021cc3974ae8f76d86673c3f824b62c79c4b41f40eaa8943738f03300f68cbe175468eb235a9ff0e6537f8714e97e8f08ca444e41191063b5fabd156e85dcf66606b81dad4a95065584b3e0658c20a706eaf4a0777da4d2e0cd2a0fca60109c2b4403db3f03cd4781c1fbb0272202bcb11687808c50cb98f64b7f3fd3d43333bb5a061b9e377090abb1e0a885cb26b73c163e63ff6451ff2f4ec8249c7e152bd03973a1e964e2b5b235281a938399a112a24529e383a560dc50bb1b622ad74ef35658dcb10ffe022568ac3ffae5b465a8ed7643e8561b352ee9944a35d882c712b187788a0abae5a22f:
|
||||||
|
4cb9df7ce6fae9d62ba09e8eb70e4c969bdeafcb5ec7d7024326e6603b0621bf018069dd0eb44055a35cd8c77c37ca9fb1ad2417271385e134b2f4e81f52033c:018069dd0eb44055a35cd8c77c37ca9fb1ad2417271385e134b2f4e81f52033c:14627d6ea0e7895460759476dc74c42800ceef994327518151490d9df23067914e44788a12768ccb25471b9c3ba9d14fb436dcba38429b3a0456877763c49175d0e082683e07a9058f3685c6279307b2303d1221b9c29793d8a4877f6df51587384dadf751c5f7bfbd207d519622c37b51ceeee2c20d8269f8cb88d3fe43d6d434d5bbd0e203c1532d97ba552147227496c87f67b50bb76193add0144df1c176657585408362ca2ed04ad62acf1c25e341dfd1498d85b4b1349a8b0b9b02c43523c55853419bfed37d5a2cdf17dfbf1a3bd7759d6ae180f9d27dcd9a8933e29a7c0a30771eea7c2e0fa242925d2336dce585629057d844323964f6d3d11ff0b3f829a3be8c9f0468a6823d8e70ab5a2da21e15fa8b041a29812222e9c30b2bd9a12d1fdee6f87876e8ce81009637a8bb2236129a47ca74289ee4aad429ffe29f47430241ca8cc3848b7200fd6e1470651a9a0a6f72c9033e831df051408a6260f65cbaf6e012b18e:e33c07836c537d6bfbd0f4592d6e35b163499ba78dc7ffcec565d04f9a7db781943e29e6ce76763e9baddf57437fd9c6b03239a6e6850e4502a356c2e12c370514627d6ea0e7895460759476dc74c42800ceef994327518151490d9df23067914e44788a12768ccb25471b9c3ba9d14fb436dcba38429b3a0456877763c49175d0e082683e07a9058f3685c6279307b2303d1221b9c29793d8a4877f6df51587384dadf751c5f7bfbd207d519622c37b51ceeee2c20d8269f8cb88d3fe43d6d434d5bbd0e203c1532d97ba552147227496c87f67b50bb76193add0144df1c176657585408362ca2ed04ad62acf1c25e341dfd1498d85b4b1349a8b0b9b02c43523c55853419bfed37d5a2cdf17dfbf1a3bd7759d6ae180f9d27dcd9a8933e29a7c0a30771eea7c2e0fa242925d2336dce585629057d844323964f6d3d11ff0b3f829a3be8c9f0468a6823d8e70ab5a2da21e15fa8b041a29812222e9c30b2bd9a12d1fdee6f87876e8ce81009637a8bb2236129a47ca74289ee4aad429ffe29f47430241ca8cc3848b7200fd6e1470651a9a0a6f72c9033e831df051408a6260f65cbaf6e012b18e:
|
||||||
|
a136e009d53e5ef59d0946bc175663a86bc0fcd29eadd95cfc9d266037b1e4fb9c1806ec0454f58314eb8397d64287dee386640d8491aba364607688841715a0:9c1806ec0454f58314eb8397d64287dee386640d8491aba364607688841715a0:a49d1c3d49e13c2eda56868a8824aa9f8d2bf72f21955ebafd07b3bdc8e924de20936cee513d8a64a47173a3bd659eff1accff8244b26aae1a0c27fa891bf4d85e8fb1b76a6cab1e7f74c89ee07bb40d714326f09b3fd40632fad208ea816f9072028c14b5b54ecc1c5b7fc809e7e0786e2f11495e76017eb62aa4563f3d00ee84348d9838cd17649f6929a6d206f60e6fc82e0c3464b27e0e6abd22f4469bdfd4cb54f77e329b80f71bf42129ec13c9dfe192adfaa42ee3ddeeda385816fbad5f411938c63b560f4ecd94534be7d98725cd94c99ce492f0f069ba0ec08f877a7812ef27ae19d7a77be63f66bcf8d6cf3a1a61fc9cfef104c7462a21ca7f03afb5bb1ac8c75124b554e8d044b810d95ff8c9dd09a34484d8c4b6c95f95c3c22823f52ce844293724d5259191f1ba0929e2acdbb8b9a7a8adf0c52e78acdfdf057b0985881afbed4dbebdebbdae0a2b63bd4e90f96afdcbbd78f506309f9bdb650013cb73faed73904e:bc094ba91c115dee15d753361a75f3f03d6af45c92157e95dbe8d32194b6c5ce72b9dc66f73df12dca0b639f3e791d478616a1f8d7359a42c8eae0dda16b1606a49d1c3d49e13c2eda56868a8824aa9f8d2bf72f21955ebafd07b3bdc8e924de20936cee513d8a64a47173a3bd659eff1accff8244b26aae1a0c27fa891bf4d85e8fb1b76a6cab1e7f74c89ee07bb40d714326f09b3fd40632fad208ea816f9072028c14b5b54ecc1c5b7fc809e7e0786e2f11495e76017eb62aa4563f3d00ee84348d9838cd17649f6929a6d206f60e6fc82e0c3464b27e0e6abd22f4469bdfd4cb54f77e329b80f71bf42129ec13c9dfe192adfaa42ee3ddeeda385816fbad5f411938c63b560f4ecd94534be7d98725cd94c99ce492f0f069ba0ec08f877a7812ef27ae19d7a77be63f66bcf8d6cf3a1a61fc9cfef104c7462a21ca7f03afb5bb1ac8c75124b554e8d044b810d95ff8c9dd09a34484d8c4b6c95f95c3c22823f52ce844293724d5259191f1ba0929e2acdbb8b9a7a8adf0c52e78acdfdf057b0985881afbed4dbebdebbdae0a2b63bd4e90f96afdcbbd78f506309f9bdb650013cb73faed73904e:
|
||||||
|
ff0f1c57dd884fbeea6e2917282b79ba67f8a6851267b9f4636dafda33bd2b5bfef6378ad12a7c252fa6eb742b05064b41530ff019dc680ab544c027ea2836e7:fef6378ad12a7c252fa6eb742b05064b41530ff019dc680ab544c027ea2836e7:522a5e5eff5b5e98fad6878a9d72df6eb318622610a1e1a48183f5590ecef5a6df671b28be91c88cdf7ae2881147fe6c37c28b43f64cf981c455c59e765ce94e1b6491631deaeef6d1da9ebca88643c77f83eae2cfdd2d97f604fe45081d1be5c4ae2d875996b8b6fecd707d3fa219a93ba0488e55247b405e330cfb97d31a1361c9b2084bdb13fb0c058925db8c3c649c9a3e937b533cc6310fa3b16126fb3cc9bb2b35c5c8300015488a30fadca3c8871fa70dfdc7055bf8e631f20c9b2528311e324a7c4edd5462079f3441c9ecf55fa999e731372344fdc0d413e417aaa001a1b2d3d9bc000fec1b02bd7a88a812d9d8a66f9464764c070c93041eefb17ce74eff6d4aff75f0cbf6a789a9ecde74abe33130fca0da853aa7c3313ada3f0ae2f595c6796a93685e729dd18a669d6381825ab3f36a391e7525b2a807a52fa5ec2a030a8cf3b77337ac41fceb580e845eed655a48b547238c2e8137c92f8c27e585caad3106eee3814a:d5008486726cce330a29dd7e4d7474d735798201afd1206feb869a112e5b43523c06976761be3cf9b2716378273c94f93572a7d2b8982634e0755c632b449008522a5e5eff5b5e98fad6878a9d72df6eb318622610a1e1a48183f5590ecef5a6df671b28be91c88cdf7ae2881147fe6c37c28b43f64cf981c455c59e765ce94e1b6491631deaeef6d1da9ebca88643c77f83eae2cfdd2d97f604fe45081d1be5c4ae2d875996b8b6fecd707d3fa219a93ba0488e55247b405e330cfb97d31a1361c9b2084bdb13fb0c058925db8c3c649c9a3e937b533cc6310fa3b16126fb3cc9bb2b35c5c8300015488a30fadca3c8871fa70dfdc7055bf8e631f20c9b2528311e324a7c4edd5462079f3441c9ecf55fa999e731372344fdc0d413e417aaa001a1b2d3d9bc000fec1b02bd7a88a812d9d8a66f9464764c070c93041eefb17ce74eff6d4aff75f0cbf6a789a9ecde74abe33130fca0da853aa7c3313ada3f0ae2f595c6796a93685e729dd18a669d6381825ab3f36a391e7525b2a807a52fa5ec2a030a8cf3b77337ac41fceb580e845eed655a48b547238c2e8137c92f8c27e585caad3106eee3814a:
|
||||||
|
0bc6af64de5709d3dbc28f7ef6d3fe28b6de529f08f5857ccb910695de454f56fb491fc900237bdc7e9a119f27150cd911935cd3628749ff40ef41f3955bc8ac:fb491fc900237bdc7e9a119f27150cd911935cd3628749ff40ef41f3955bc8ac:ac7886e4f4172a22c95e8eea37437b375d72accedcee6cc6e816763301a2d8ef4d6f31a2c1d635818b7026a395ce0dafd71c5180893af76b7ea056c972d680eca01dcbdbae6b26f1c5f33fc988b824fbbe00cacc316469a3bae07aa7c8885af7f65f42e75cef94dbb9aab4825143c85070e7716b7612f64ef0b0166011d23eb5654aa098b02d8d71e57c8fa17bff2fe97dc8193177eadc09fb192d80aa92afa98720d4614817ff3c39d3acce18906fa3de09618931d0d7a60c4429cbfa20cf165c947929ac293ae6c06e7e8f25f1264291e3e1c98f5d93e6ecc2389bc60dbbf4a621b132c552a99c95d26d8d1af61138b570a0de4b497ebe8051c7273a98e6e7876d0b327503af3cb2cc4091ce1925cb2f2957f4ec56ee90f8a09dd57d6e83067a356a4cfe65b1b7a4465da2ab133b0efb5e7d4dbb811bcbbde712afbf0f7dd3f326222284b8c74eac7ad6257fa8c632b7da2559a6266e91e0ef90dbb0aa968f75376b693fcaa5da342221:dbc7134d1cd6b0813b53352714b6df939498e91cf37c324337d9c088a1b998347d26185b430900412929e4f63e910379fc42e355a4e98f6fee27dafad1957206ac7886e4f4172a22c95e8eea37437b375d72accedcee6cc6e816763301a2d8ef4d6f31a2c1d635818b7026a395ce0dafd71c5180893af76b7ea056c972d680eca01dcbdbae6b26f1c5f33fc988b824fbbe00cacc316469a3bae07aa7c8885af7f65f42e75cef94dbb9aab4825143c85070e7716b7612f64ef0b0166011d23eb5654aa098b02d8d71e57c8fa17bff2fe97dc8193177eadc09fb192d80aa92afa98720d4614817ff3c39d3acce18906fa3de09618931d0d7a60c4429cbfa20cf165c947929ac293ae6c06e7e8f25f1264291e3e1c98f5d93e6ecc2389bc60dbbf4a621b132c552a99c95d26d8d1af61138b570a0de4b497ebe8051c7273a98e6e7876d0b327503af3cb2cc4091ce1925cb2f2957f4ec56ee90f8a09dd57d6e83067a356a4cfe65b1b7a4465da2ab133b0efb5e7d4dbb811bcbbde712afbf0f7dd3f326222284b8c74eac7ad6257fa8c632b7da2559a6266e91e0ef90dbb0aa968f75376b693fcaa5da342221:
|
||||||
|
2f5e83bd5b412e71ae3e9084cd369efcc79bf6037c4b174dfd6a11fb0f5da218a22a6da29a5ef6240c49d8896e3a0f1a4281a266c77d383ee6f9d25ffacbb872:a22a6da29a5ef6240c49d8896e3a0f1a4281a266c77d383ee6f9d25ffacbb872:b766273f060ef3b2ae3340454a391b426bc2e97264f8674553eb00dd6ecfdd59b611d8d662929fec710d0e462020e12cdbf9c1ec8858e85671acf8b7b14424ce92079d7d801e2ad9acac036bc8d2dfaa72aa839bff30c0aa7e414a882c00b645ff9d31bcf5a54382def4d0142efa4f06e823257ff132ee968cdc6738c53f53b84c8df76e9f78dd5056cf3d4d5a80a8f84e3edec48520f2cb4583e708539355ef7aa86fb5a0e87a94dcf14f30a2cca568f139d9ce59eaf459a5c5916cc8f20b26aaf6c7c029379aedb05a07fe585ccac60307c1f58ca9f859157d06d06baa394aace79d51b8cb38cfa2598141e245624e5ab9b9d68731173348905315bf1a5ad61d1e8adaeb810e4e8a86d7c13537b0be860ab2ed35b73399b8808aa91d750f77943f8a8b7e89fdb50728aa3dbbd8a41a6e00756f438c9b9e9d55872df5a9068add8a972b7e43edad9ced2237ca1367be4b7cdb66a54ea12eef129471158610eaf28f99f7f686557dcdf644ea:9f80922bc8db32d0cc43f9936affebe7b2bc35a5d82277cd187b5d50dc7fc4c4832fffa34e9543806b485c04548e7c75429425e14d55d91fc1052efd8667430bb766273f060ef3b2ae3340454a391b426bc2e97264f8674553eb00dd6ecfdd59b611d8d662929fec710d0e462020e12cdbf9c1ec8858e85671acf8b7b14424ce92079d7d801e2ad9acac036bc8d2dfaa72aa839bff30c0aa7e414a882c00b645ff9d31bcf5a54382def4d0142efa4f06e823257ff132ee968cdc6738c53f53b84c8df76e9f78dd5056cf3d4d5a80a8f84e3edec48520f2cb4583e708539355ef7aa86fb5a0e87a94dcf14f30a2cca568f139d9ce59eaf459a5c5916cc8f20b26aaf6c7c029379aedb05a07fe585ccac60307c1f58ca9f859157d06d06baa394aace79d51b8cb38cfa2598141e245624e5ab9b9d68731173348905315bf1a5ad61d1e8adaeb810e4e8a86d7c13537b0be860ab2ed35b73399b8808aa91d750f77943f8a8b7e89fdb50728aa3dbbd8a41a6e00756f438c9b9e9d55872df5a9068add8a972b7e43edad9ced2237ca1367be4b7cdb66a54ea12eef129471158610eaf28f99f7f686557dcdf644ea:
|
||||||
|
722a2da50e42c11a61c9afac7be1a2fed2267d650f8f7d8e5bc706b807c1b91dfd0b964562f823721e649c3fedb432a76f91e0aead7c61d35f95ed7726d78589:fd0b964562f823721e649c3fedb432a76f91e0aead7c61d35f95ed7726d78589:173e8bb885e1f9081404acac999041d2ecfcb73f945e0db36e631d7cd1ab999eb717f34bf07874bf3d34e2530eb6085f4a9f88ae1b0f7d80f221456a8e9a8890b91a50192deaaacc0a1a615a87841e2c5a9e057957af6e48e78cc86198e32e7aa24dcf6cffa329bc72606d65b11682c8ba736cce22a05785df1146331e41609cf9ca711cf464958297138b58a9073f3bbf06ad8a85d135de66652104d88b49d27ad41e59bcc44c7fab68f53f0502e293ffcabaaf755927dfdffbfde3b35c080b5de4c8b785f4da64ef357bc0d1466a6a96560c3c4f3e3c0b563a003f5f95f237171bce1a001771a04ede7cdd9b8ca770fd36ef90e9fe0000a8d7685fd153cc7282de95920a8f8f0898d00bf0c6c933fe5bb9653ff146c4e2acd1a2e0c23c1244844dacf8652716302c2032f9c114679ed26b3ee3ab4a7b18bc4e3071f0977db57cd0ac68c0727a09b4f125fb64af2850b26c8a484263334e2da902d744737044e79ab1cf5b2f93a022b63d40cd:c2695a57172aaa31bd0890f231ca8eeec0287a87172669a899ad0891cea4c47579b50420e791cdec8c182c8a0e8dde21b2480b0cfd8111e28e5603347a352d04173e8bb885e1f9081404acac999041d2ecfcb73f945e0db36e631d7cd1ab999eb717f34bf07874bf3d34e2530eb6085f4a9f88ae1b0f7d80f221456a8e9a8890b91a50192deaaacc0a1a615a87841e2c5a9e057957af6e48e78cc86198e32e7aa24dcf6cffa329bc72606d65b11682c8ba736cce22a05785df1146331e41609cf9ca711cf464958297138b58a9073f3bbf06ad8a85d135de66652104d88b49d27ad41e59bcc44c7fab68f53f0502e293ffcabaaf755927dfdffbfde3b35c080b5de4c8b785f4da64ef357bc0d1466a6a96560c3c4f3e3c0b563a003f5f95f237171bce1a001771a04ede7cdd9b8ca770fd36ef90e9fe0000a8d7685fd153cc7282de95920a8f8f0898d00bf0c6c933fe5bb9653ff146c4e2acd1a2e0c23c1244844dacf8652716302c2032f9c114679ed26b3ee3ab4a7b18bc4e3071f0977db57cd0ac68c0727a09b4f125fb64af2850b26c8a484263334e2da902d744737044e79ab1cf5b2f93a022b63d40cd:
|
||||||
|
5fe9c3960ed5bd374cc94d42357e6a24dc7e3060788f726365defacf13cd12da0ce7b155c8b20ebdaacdc2aa23627e34b1f9ace980650a2530c7607d04814eb4:0ce7b155c8b20ebdaacdc2aa23627e34b1f9ace980650a2530c7607d04814eb4:c9490d83d9c3a9370f06c91af001685a02fe49b5ca667733fff189eee853ec1667a6c1b6c787e9244812d2d532866ab74dfc870d6f14033b6bcd39852a3900f8f08cd95a74cb8cbe02b8b8b51e993a06adfebd7fc9854ae5d29f4df9642871d0c5e470d903cfbcbd5adb3275628f28a80bf8c0f0376687dae673bf7a8547e80d4a9855ae2572fc2b205dc8a198016ddc9b50995f5b39f368f540504a551803d6dd5f874828e5541ded052894d9e2dc5e6aa351087e790c0dd5d9c4decb217e4db81c98a184b264e6daeac0f11e074cae2bfc899f54b419c65dcc22664a915fbfffac35cee0f286eb7b144933db933e16c4bcb650d537722489de236373fd8d65fc86118b6def37ca4608bc6ce927b65436ffda7f02bfbf88b045ae7d2c2b45a0b30c8f2a04df953221088c555fe9a5df260982a3d64df194ee952fa9a98c31b96493db6180d13d67c36716f95f8c0bd7a039ad990667ca34a83ac1a18c37dd7c7736aa6b9b6fc2b1ac0ce119ef77:379f9c54c413af0d192e9bc736b29da9d521e7ba7841d309f9bcc1e742ec4308fe9f7ba51e0b22aed487cb4aa3913b9bebfb3aacd38f4039f9bbbebe1ad80002c9490d83d9c3a9370f06c91af001685a02fe49b5ca667733fff189eee853ec1667a6c1b6c787e9244812d2d532866ab74dfc870d6f14033b6bcd39852a3900f8f08cd95a74cb8cbe02b8b8b51e993a06adfebd7fc9854ae5d29f4df9642871d0c5e470d903cfbcbd5adb3275628f28a80bf8c0f0376687dae673bf7a8547e80d4a9855ae2572fc2b205dc8a198016ddc9b50995f5b39f368f540504a551803d6dd5f874828e5541ded052894d9e2dc5e6aa351087e790c0dd5d9c4decb217e4db81c98a184b264e6daeac0f11e074cae2bfc899f54b419c65dcc22664a915fbfffac35cee0f286eb7b144933db933e16c4bcb650d537722489de236373fd8d65fc86118b6def37ca4608bc6ce927b65436ffda7f02bfbf88b045ae7d2c2b45a0b30c8f2a04df953221088c555fe9a5df260982a3d64df194ee952fa9a98c31b96493db6180d13d67c36716f95f8c0bd7a039ad990667ca34a83ac1a18c37dd7c7736aa6b9b6fc2b1ac0ce119ef77:
|
||||||
|
ec2fa541ac14b414149c3825eaa7001b795aa1957d4040dda92573904afa7ee471b363b2408404d7beecdef1e1f511bb6084658b532f7ea63d4e3f5f01c61d31:71b363b2408404d7beecdef1e1f511bb6084658b532f7ea63d4e3f5f01c61d31:2749fc7c4a729e0e0ad71b5b74eb9f9c534ebd02ffc9df4374d813bdd1ae4eb87f1350d5fdc563934515771763e6c33b50e64e0cd114573031d2186b6eca4fc802cddc7cc51d92a61345a17f6ac38cc74d84707a5156be9202dee3444652e79bae7f0d31bd17567961f65dd01a8e4bee38331938ce4b2b550691b99a4bc3c072d186df4b3344a5c8fbfbb9fd2f355f6107e410c3d0c798b68d3fb9c6f7ab5fe27e70871e86767698fe35b77ead4e435a9402cc9ed6a2657b059be0a21003c048bbf5e0ebd93cbb2e71e923cf5c728d1758cd817ad74b454a887126d653b95a7f25e5293b768c9fc5a9c35a2372e3741bc90fd66301427b10824bb4b1e9110bfba84c21a40eb8fed4497e91dc3ffd0438c514c0a8cb4cac6ad0256bf11d5aa7a9c7c00b669b015b0bf81425a21413e2ffb6edc0bd78e385c44fd74558e511c2c25fee1fec18d3990b8690300fa711e93d9854668f0187065e76e7113ae763c30ddd86720b5546a6c3c6f1c43bc67b14:84d18d56f964e3776759bba92c510c2b6d574555c3cddade212da90374554991e7d77e278d63e34693e1958078cc3685f8c41c1f5342e351899638ef612114012749fc7c4a729e0e0ad71b5b74eb9f9c534ebd02ffc9df4374d813bdd1ae4eb87f1350d5fdc563934515771763e6c33b50e64e0cd114573031d2186b6eca4fc802cddc7cc51d92a61345a17f6ac38cc74d84707a5156be9202dee3444652e79bae7f0d31bd17567961f65dd01a8e4bee38331938ce4b2b550691b99a4bc3c072d186df4b3344a5c8fbfbb9fd2f355f6107e410c3d0c798b68d3fb9c6f7ab5fe27e70871e86767698fe35b77ead4e435a9402cc9ed6a2657b059be0a21003c048bbf5e0ebd93cbb2e71e923cf5c728d1758cd817ad74b454a887126d653b95a7f25e5293b768c9fc5a9c35a2372e3741bc90fd66301427b10824bb4b1e9110bfba84c21a40eb8fed4497e91dc3ffd0438c514c0a8cb4cac6ad0256bf11d5aa7a9c7c00b669b015b0bf81425a21413e2ffb6edc0bd78e385c44fd74558e511c2c25fee1fec18d3990b8690300fa711e93d9854668f0187065e76e7113ae763c30ddd86720b5546a6c3c6f1c43bc67b14:
|
||||||
|
6132692a5ef27bf476b1e991e6c431a8c764f1aebd470282db3321bb7cb09c207a2d166184f9e5f73bea454486b041ceb5fc2314a7bd59cb718e79f0ec989d84:7a2d166184f9e5f73bea454486b041ceb5fc2314a7bd59cb718e79f0ec989d84:a9c0861665d8c2de06f9301da70afb27b3024b744c6b38b24259294c97b1d1cb4f0dcf7575a8ed454e2f0980f50313a77363415183fe9677a9eb1e06cb6d34a467cb7b0758d6f55c564b5ba15603e202b18856d89e72a23ab07d8853ff77da7aff1caebd7959f2c710ef31f5078a9f2cdae92641a1cc5f74d0c143ec42afbaa5f378a9e10d5bf74587fa5f49c156233247dafd3929acde888dc684337e40cdc5932e7eb73ffcc90b85c0ad460416691aefbd7efd07b657c350946a0e366b37a6c8089aba5c5fe3bbca064afbe9d47fbc83914af1cb43c2b2efa98e0a43be32ba823202001def36817251b65f9b0506cef6683642a46ed612f8ca81ee97bb04d317b517343ade2b77126d1f02a87b7604c8653b6748cf5488fa6d43df809faa19e69292d38c5d397dd8e20c7af7c5334ec977f5010a0f7cb5b89479ca06db4d12627f067d6c42186a6b1f8742f36ae709ba720e3cd898116666d81b190b9b9d2a72202cb690a03f3310429a71dc048cde:eb677f3347e1a1ea929efdf62bf9105a6c8f4993033b4f6d03cb0dbf9c742b270704e383ab7c0676bdb1ad0ce9b16673083c9602ec10ae1dd98e8748b336440ba9c0861665d8c2de06f9301da70afb27b3024b744c6b38b24259294c97b1d1cb4f0dcf7575a8ed454e2f0980f50313a77363415183fe9677a9eb1e06cb6d34a467cb7b0758d6f55c564b5ba15603e202b18856d89e72a23ab07d8853ff77da7aff1caebd7959f2c710ef31f5078a9f2cdae92641a1cc5f74d0c143ec42afbaa5f378a9e10d5bf74587fa5f49c156233247dafd3929acde888dc684337e40cdc5932e7eb73ffcc90b85c0ad460416691aefbd7efd07b657c350946a0e366b37a6c8089aba5c5fe3bbca064afbe9d47fbc83914af1cb43c2b2efa98e0a43be32ba823202001def36817251b65f9b0506cef6683642a46ed612f8ca81ee97bb04d317b517343ade2b77126d1f02a87b7604c8653b6748cf5488fa6d43df809faa19e69292d38c5d397dd8e20c7af7c5334ec977f5010a0f7cb5b89479ca06db4d12627f067d6c42186a6b1f8742f36ae709ba720e3cd898116666d81b190b9b9d2a72202cb690a03f3310429a71dc048cde:
|
||||||
|
f219b2101164aa9723bde3a7346f68a35061c01f9782072580ba32df903ba891f66b920d5aa1a6085495a1480539beba01ffe60e6a6388d1b2e8eda23355810e:f66b920d5aa1a6085495a1480539beba01ffe60e6a6388d1b2e8eda23355810e:015577d3e4a0ec1ab25930106343ff35ab4f1e0a8a2d844aadbb70e5fc5348ccb679c2295c51d702aaae7f6273ce70297b26cb7a253a3db94332e86a15b4a64491232791f7a8b082ee2834af30400e804647a532e9c454d2a0a7320130ab6d4d860073a34667ac25b7e5e2747ba9f5c94594fb68377ae260369c40713b4e32f23195bf91d3d7f1a2719bf408aad8d8a347b112e84b118817cb06513344021763035272a7db728a0ccdaa949c61715d0764140b3e8c01d20ff1593c7f2d55c4e82a1c0cb1ea58442bf80a741bca91f58ab0581b498ee9fe3c92ca654148ef75313543d1aff382befe1a93b02190ce0102175158e2071d02bacad8dbe9fb940fcb610c105ad52c80feb1ec4e524f4c0ec7983e9ce696fa4fcf4bf0514b8f0432b17d5448fc426fea2b01ac7b26c2aed769927534da22576fc1bba726e9d65be01b59f60a648ace2fc3e5e275789fa637cbbd84be3d6ac24457a6292cd656c7b569a52ffea7916b8d04b4f4a75be7ac95142f:17f0127ca3bafa5f4ee959cd60f772be87a0034961517e39a0a1d0f4b9e26db1336e60c82b352c4cbacdbbd11771c3774f8cc5a1a795d6e4f4ebd51def36770b015577d3e4a0ec1ab25930106343ff35ab4f1e0a8a2d844aadbb70e5fc5348ccb679c2295c51d702aaae7f6273ce70297b26cb7a253a3db94332e86a15b4a64491232791f7a8b082ee2834af30400e804647a532e9c454d2a0a7320130ab6d4d860073a34667ac25b7e5e2747ba9f5c94594fb68377ae260369c40713b4e32f23195bf91d3d7f1a2719bf408aad8d8a347b112e84b118817cb06513344021763035272a7db728a0ccdaa949c61715d0764140b3e8c01d20ff1593c7f2d55c4e82a1c0cb1ea58442bf80a741bca91f58ab0581b498ee9fe3c92ca654148ef75313543d1aff382befe1a93b02190ce0102175158e2071d02bacad8dbe9fb940fcb610c105ad52c80feb1ec4e524f4c0ec7983e9ce696fa4fcf4bf0514b8f0432b17d5448fc426fea2b01ac7b26c2aed769927534da22576fc1bba726e9d65be01b59f60a648ace2fc3e5e275789fa637cbbd84be3d6ac24457a6292cd656c7b569a52ffea7916b8d04b4f4a75be7ac95142f:
|
||||||
|
fc180035aec0f5ede7bda93bf77ade7a81ed06de07ee2e3aa8576be81608610a4f215e948cae243ee3143b80282ad792c780d2a6b75060ca1d290ca1a8e3151f:4f215e948cae243ee3143b80282ad792c780d2a6b75060ca1d290ca1a8e3151f:b5e8b01625664b222339e0f05f93a990ba48b56ae65439a17520932df011721e284dbe36f98631c066510098a68d7b692a3863e99d58db76ca5667c8043cb10bd7abbaf506529fbb23a5166be038affdb9a234c4f4fcf43bddd6b8d2ce772dd653ed115c095e232b269dd4888d2368cb1c66be29dd383fca67f66765b296564e37555f0c0e484504c591f006ea8533a12583ad2e48318ff6f324ecaf804b1bae04aa896743e67ef61ca383d58e42acfc6410de30776e3ba262373b9e1441943955101a4e768231ad9c6529eff6118dde5df02f94b8d6df2d99f27863b517243a579e7aaff311ea3a0282e47ca876fabc2280fce7adc984dd0b30885b1650f1471dfcb0522d49fec7d042f32a93bc368f076006ea01ec1c7412bf66f62dc88de2c0b74701a5614e855e9fa728fb1f1171385f96afbde70dea02e9aa94dc21848c26302b50ae91f9693a1864e4e095ae03cdc22ad28a0eb7db596779246712fab5f5da327efec3e79612de0a6ccaa536759b8e:a43a71c3a19c35660dae6f31a254b8c0ea3593fc8fca74d13640012b9e9473d4afe070db01e7fb399bf4ca6070e062180011285a67dd6858b761e46c6bd32004b5e8b01625664b222339e0f05f93a990ba48b56ae65439a17520932df011721e284dbe36f98631c066510098a68d7b692a3863e99d58db76ca5667c8043cb10bd7abbaf506529fbb23a5166be038affdb9a234c4f4fcf43bddd6b8d2ce772dd653ed115c095e232b269dd4888d2368cb1c66be29dd383fca67f66765b296564e37555f0c0e484504c591f006ea8533a12583ad2e48318ff6f324ecaf804b1bae04aa896743e67ef61ca383d58e42acfc6410de30776e3ba262373b9e1441943955101a4e768231ad9c6529eff6118dde5df02f94b8d6df2d99f27863b517243a579e7aaff311ea3a0282e47ca876fabc2280fce7adc984dd0b30885b1650f1471dfcb0522d49fec7d042f32a93bc368f076006ea01ec1c7412bf66f62dc88de2c0b74701a5614e855e9fa728fb1f1171385f96afbde70dea02e9aa94dc21848c26302b50ae91f9693a1864e4e095ae03cdc22ad28a0eb7db596779246712fab5f5da327efec3e79612de0a6ccaa536759b8e:
|
||||||
|
a2836a65427912122d25dcdfc99d7046fe9b53d5c1bb23617f11890e94ca93ed8c12bda214c8abb2286acffbf8112425040aab9f4d8bb7870b98da0159e882f1:8c12bda214c8abb2286acffbf8112425040aab9f4d8bb7870b98da0159e882f1:813d6061c56eae0ff53041c0244aa5e29e13ec0f3fb428d4beb8a99e04bca8c41bddb0db945f487efe38f2fc14a628fafa2462f860e4e34250eb4e93f139ab1b74a2614519e41ee2403be427930ab8bc82ec89ceafb60905bd4ddbbd13bdb19654314fc92373140b962e2258e038d71b9ec66b84ef8319e03551cb707e747f6c40ad476fbefdce71f3a7b67a1af1869bc6440686e7e0855e4f369d1d88b8099fba54714678627bba1aff41e7707bc97eddf890b0c08dce3e9800d24c6f61092ce28d481b5dea5c096c55d72f8946009131fb968e2bc8a054d825adab76740dcf0d758c8bf54ff38659e71b32bfe2e615aaabb0f5293085649cf60b9847bc62011ce3878af628984a5840a4ad5dae3702db367da0f8a165fed0517eb5c442b0145330241b97eeca733ba6688b9c129a61cd1236aff0e27bcf98c28b0fbeea55a3d7c7193d644b2749f986bd46af8938e8faaeafbd9cec3612ab005bd7c3eeafe9a31279ca6102560666ba16136ff1452f850adb:e6a9a6b436559a4320c45c0c2c4a2aedecb90d416d52c82680ac7330d062aebef3e9ac9f2c5ffa455c9be113013a2b282e5600fd306435ada83b1e48ba2a3605813d6061c56eae0ff53041c0244aa5e29e13ec0f3fb428d4beb8a99e04bca8c41bddb0db945f487efe38f2fc14a628fafa2462f860e4e34250eb4e93f139ab1b74a2614519e41ee2403be427930ab8bc82ec89ceafb60905bd4ddbbd13bdb19654314fc92373140b962e2258e038d71b9ec66b84ef8319e03551cb707e747f6c40ad476fbefdce71f3a7b67a1af1869bc6440686e7e0855e4f369d1d88b8099fba54714678627bba1aff41e7707bc97eddf890b0c08dce3e9800d24c6f61092ce28d481b5dea5c096c55d72f8946009131fb968e2bc8a054d825adab76740dcf0d758c8bf54ff38659e71b32bfe2e615aaabb0f5293085649cf60b9847bc62011ce3878af628984a5840a4ad5dae3702db367da0f8a165fed0517eb5c442b0145330241b97eeca733ba6688b9c129a61cd1236aff0e27bcf98c28b0fbeea55a3d7c7193d644b2749f986bd46af8938e8faaeafbd9cec3612ab005bd7c3eeafe9a31279ca6102560666ba16136ff1452f850adb:
|
||||||
|
f051af426d0c3282fafc8bf912ade1c24211a95ad200e1eef549320e1cb1a252fa87955e0ea13dde49d83dc22e63a2bdf1076725c2cc7f93c76511f28e7944f2:fa87955e0ea13dde49d83dc22e63a2bdf1076725c2cc7f93c76511f28e7944f2:b48d9f84762b3bcc66e96d76a616fa8fe8e01695251f47cfc1b7b17d60dc9f90d576ef64ee7d388504e2c9079638165a889696471c989a876f8f13b63b58d531fea4dd1229fc631668a047bfae2da281feae1b6de3ebe280abe0a82ee00fbfdc22ce2d10e06a0492ff1404dfc094c40b203bf55721dd787ed4e91d5517aaf58d3bdd35d44a65ae6ba75619b339b650518cefcc17493de27a3b5d41788f87edbde72610f181bf06e208e0eb7cdfe881d91a2d6cc77aa19c0fcf330fedb44675d800eb8cff9505d8887544a503cbe373c4847b19e8f3995726efd6649858595c57ccaf0cbc9eb25de83ba046bc9f1838ac7b8953dd81b81ac0f68d0e9338cb55402552afb6bc16949351b926d151a82efc695e8d7da0dd55099366789718ccbf36030bd2c3c109399be26cdb8b9e2a155f3b2cb1bfa71ab69a23625a4ac118fe91cb2c19788cf52a71d730d576b421d96982a51a2991daec440cda7e6cc3282b8312714278b819bfe2387eb96aa91d40173034f428:b8f713578a64466719aceb432fce302a87cf066bf3e102a350616921a840964bfc7e685d8fd17455ac3eb4861edcb8979d35e3a4bd82a078cd707721d733400eb48d9f84762b3bcc66e96d76a616fa8fe8e01695251f47cfc1b7b17d60dc9f90d576ef64ee7d388504e2c9079638165a889696471c989a876f8f13b63b58d531fea4dd1229fc631668a047bfae2da281feae1b6de3ebe280abe0a82ee00fbfdc22ce2d10e06a0492ff1404dfc094c40b203bf55721dd787ed4e91d5517aaf58d3bdd35d44a65ae6ba75619b339b650518cefcc17493de27a3b5d41788f87edbde72610f181bf06e208e0eb7cdfe881d91a2d6cc77aa19c0fcf330fedb44675d800eb8cff9505d8887544a503cbe373c4847b19e8f3995726efd6649858595c57ccaf0cbc9eb25de83ba046bc9f1838ac7b8953dd81b81ac0f68d0e9338cb55402552afb6bc16949351b926d151a82efc695e8d7da0dd55099366789718ccbf36030bd2c3c109399be26cdb8b9e2a155f3b2cb1bfa71ab69a23625a4ac118fe91cb2c19788cf52a71d730d576b421d96982a51a2991daec440cda7e6cc3282b8312714278b819bfe2387eb96aa91d40173034f428:
|
||||||
|
a103e92672c65f81ea5da1fff1a4038788479e941d503a756f4a755201a57c1dee63a5b69641217acbaf3339da829ec071b9931e5987153514d30140837a7af4:ee63a5b69641217acbaf3339da829ec071b9931e5987153514d30140837a7af4:b1984e9eec085d524c1eb3b95c89c84ae085be5dc65c326e19025e1210a1d50edbbba5d1370cf15d68d687eb113233e0fba50f9433c7d358773950c67931db8296bbcbecec888e87e71a2f7579fad2fa162b85fb97473c456b9a5ce2956676969c7bf4c45679085b62f2c224fc7f458794273f6d12c5f3e0d06951824d1cca3e2f904559ed28e2868b366d79d94dc98667b9b5924268f3e39b1291e5abe4a758f77019dacbb22bd8196e0a83a5677658836e96ca5635055a1e63d65d036a68d87ac2fd283fdda390319909c5cc7680368848873d597f298e0c6172308030ffd452bb1363617b316ed7cd949a165dc8abb53f991aef3f3e9502c5dfe4756b7c6bfdfe89f5e00febdd6afb0402818f11cf8d1d5864fe9da1b86e39aa935831506cf2400ea7ed75bd9533b23e202fe875d7d9638c89d11cb2d6e6021ae6bd27c7754810d35cd3a61494f27b16fc794e2cd2f0d3453ada933865db78c579571f8fc5c5c6be8eaffce6a852e5b3b1c524c49313d427abcb:2aa2035c2ce5b5e6ae161e168f3ad0d6592bcf2c4a049d3ed342fceb56be9c7cb372027573ae0178e8878ebefca7b030327b8aad41857de58cb78e1a00cbac05b1984e9eec085d524c1eb3b95c89c84ae085be5dc65c326e19025e1210a1d50edbbba5d1370cf15d68d687eb113233e0fba50f9433c7d358773950c67931db8296bbcbecec888e87e71a2f7579fad2fa162b85fb97473c456b9a5ce2956676969c7bf4c45679085b62f2c224fc7f458794273f6d12c5f3e0d06951824d1cca3e2f904559ed28e2868b366d79d94dc98667b9b5924268f3e39b1291e5abe4a758f77019dacbb22bd8196e0a83a5677658836e96ca5635055a1e63d65d036a68d87ac2fd283fdda390319909c5cc7680368848873d597f298e0c6172308030ffd452bb1363617b316ed7cd949a165dc8abb53f991aef3f3e9502c5dfe4756b7c6bfdfe89f5e00febdd6afb0402818f11cf8d1d5864fe9da1b86e39aa935831506cf2400ea7ed75bd9533b23e202fe875d7d9638c89d11cb2d6e6021ae6bd27c7754810d35cd3a61494f27b16fc794e2cd2f0d3453ada933865db78c579571f8fc5c5c6be8eaffce6a852e5b3b1c524c49313d427abcb:
|
||||||
|
d47c1b4b9e50cbb71fd07d096d91d87213d44b024373044761c4822f9d9df880f4e1cb86c8ca2cfee43e58594a8778436d3ea519704e00c1bbe48bbb1c9454f8:f4e1cb86c8ca2cfee43e58594a8778436d3ea519704e00c1bbe48bbb1c9454f8:88d7009d51de3d337eef0f215ea66ab830ec5a9e6823761c3b92ad93ea341db92ece67f4ef4ceb84194ae6926c3d014b2d59781f02e0b32f9a611222cb9a5850c6957cb8079ae64e0832a1f05e5d1a3c572f9d08f1437f76bb3b83b52967c3d48c3576848891c9658d4959eb80656d26cdba0810037c8a18318ff122f8aa8985c773cb317efa2f557f1c3896bcb162df5d87681bb787e7813aa2dea3b0c564d646a92861f444ca1407efbac3d12432cbb70a1d0eaffb11741d3718fedee2b83036189a6fc45a52f74fa487c18fd264a7945f6c9e44b011f5d86613f1939b19f4f4fdf53234057be3f005ad64eebf3c8ffb58cb40956c4336df01d4424b706a0e561d601708d12485e21bcb6d799d8d1d044b400064ec0944501406e70253947006cabbdb2dd6bd8cee4497653d9113a44d4de9b68d4c526fca0b9b0c18fe50fb917fdd9a914fb816108a73a6b3fff9e654e69c9cfe02b05c6c1b9d15c4e65cf31018b8100d784633ee1888eee3572aafa6f189ea22d0:627e7ca7e34ed6331d62b9541c1ea9a9292be7b0a65d805e266b5122272a82db7d765acc7e2a290d685804922f91ed04a3c382c03ff21a1768f584413c4e5f0088d7009d51de3d337eef0f215ea66ab830ec5a9e6823761c3b92ad93ea341db92ece67f4ef4ceb84194ae6926c3d014b2d59781f02e0b32f9a611222cb9a5850c6957cb8079ae64e0832a1f05e5d1a3c572f9d08f1437f76bb3b83b52967c3d48c3576848891c9658d4959eb80656d26cdba0810037c8a18318ff122f8aa8985c773cb317efa2f557f1c3896bcb162df5d87681bb787e7813aa2dea3b0c564d646a92861f444ca1407efbac3d12432cbb70a1d0eaffb11741d3718fedee2b83036189a6fc45a52f74fa487c18fd264a7945f6c9e44b011f5d86613f1939b19f4f4fdf53234057be3f005ad64eebf3c8ffb58cb40956c4336df01d4424b706a0e561d601708d12485e21bcb6d799d8d1d044b400064ec0944501406e70253947006cabbdb2dd6bd8cee4497653d9113a44d4de9b68d4c526fca0b9b0c18fe50fb917fdd9a914fb816108a73a6b3fff9e654e69c9cfe02b05c6c1b9d15c4e65cf31018b8100d784633ee1888eee3572aafa6f189ea22d0:
|
||||||
|
fc0c32c5eb6c71ea08dc2b300cbcef18fdde3ea20f68f21733237b4ddaab900e47c37d8a080857eb8777a6c0a9a5c927303faf5c320953b5de48e462e12d0062:47c37d8a080857eb8777a6c0a9a5c927303faf5c320953b5de48e462e12d0062:a7b1e2db6bdd96b3d51475603537a76b42b04d7ebd24fe515a887658e4a352e22109335639a59e2534811f4753b70209d0e4698e9d926088826c14689681ea00fa3a2fcaa0047ced3ef287e6172502b215e56497614d86b4cb26bcd77a2e172509360ee58893d01c0d0fb4d4abfe4dbd8d2a2f54190fa2f731c1ceac6829c3ddc9bfb2ffd70c57ba0c2b22d2326fbfe7390db8809f73547ff47b86c36f2bf7454e678c4f1c0fa870bd0e30bbf3278ec8d0c5e9b64aff0af64babc19b70f4cf9a41cb8f95d3cde24f456ba3571c8f021d38e591dec05cb5d1ca7b48f9da4bd734b069a9fd106500c1f408ab7fe8e4a6e6f3ed64da0ed24b01e33df8475f95fa9ed71d04dd30b3cd823755a3401bf5afae10ee7e18ec6fe637c3793fd434b48d7145130447e00299101052558b506554ec9c399f62941c3f414cbc352caa345b930adecfaddac91ee53d1451a65e06201026325de07c931f69bba868a7c87ee23c604ec6794332917dfe2c5b69669b659706917f71eddf96:6887c6e2b98a82af5ee3dfa7ca2cb25d9c10745620a82956acba85cb57c8ec24279fa42f092359a1b6bbeafba050f14b6288209e6ef7bc1e0a2b872c1138f305a7b1e2db6bdd96b3d51475603537a76b42b04d7ebd24fe515a887658e4a352e22109335639a59e2534811f4753b70209d0e4698e9d926088826c14689681ea00fa3a2fcaa0047ced3ef287e6172502b215e56497614d86b4cb26bcd77a2e172509360ee58893d01c0d0fb4d4abfe4dbd8d2a2f54190fa2f731c1ceac6829c3ddc9bfb2ffd70c57ba0c2b22d2326fbfe7390db8809f73547ff47b86c36f2bf7454e678c4f1c0fa870bd0e30bbf3278ec8d0c5e9b64aff0af64babc19b70f4cf9a41cb8f95d3cde24f456ba3571c8f021d38e591dec05cb5d1ca7b48f9da4bd734b069a9fd106500c1f408ab7fe8e4a6e6f3ed64da0ed24b01e33df8475f95fa9ed71d04dd30b3cd823755a3401bf5afae10ee7e18ec6fe637c3793fd434b48d7145130447e00299101052558b506554ec9c399f62941c3f414cbc352caa345b930adecfaddac91ee53d1451a65e06201026325de07c931f69bba868a7c87ee23c604ec6794332917dfe2c5b69669b659706917f71eddf96:
|
||||||
|
a8d73d639a23cc6a967ef31bcabb5d063e53e1eab8fcc7cab9bc3a17fde9c2f88daa9f4c8b1a44691bf44521f2f7ca45dc7fc61f6a4ce6f98faa41c2a74977d1:8daa9f4c8b1a44691bf44521f2f7ca45dc7fc61f6a4ce6f98faa41c2a74977d1:fd1fac3d53313b11acd29f5a83ac11896dab2530fa47865b2295c0d99dd67c36ed8e5fa549150c794c5549efb5c1d69114d5d607b23285b7212afaab57846a54ae67b9e880e07b6586607cecf6d4eed516a3a75511fe367d88eb871e6d71b7d6aa1367a01421b1088fc2d75e44954b73625c52da8a3a183c60be9da6050f59a453caa53520593671728d431877bfaac913a765fb6a56b75290b2a8aaac34afb9217ba1b0d5850ba0fdabf80969def0feee794ceb60614e3368e63ef20e4c32d341ec9b0328ea9fe139207ed7a626ff08943b415233db7cfcc845c9b63121d4ed52ec3748ab6a1f36b2103c7dc7e9303acea4ba8af7a3e07184fb491e891ede84f0dc41cadc3973028e879acd2031afc29a16092868e2c7f539fc1b792edab195a25ab9830661346b39ef53915de4af52c421eaf172e9da76a08c283a52df907f705d7e8599c5baae0c2af380c1bb46f93484a03f28374324b278992b50b7afa02552cafa503f034f8d866e9b720271dd68ccb685a85fffd1:c4dcef1a2453939b364b340250c3129431431d5ba3f47670ab07ce680c69bf28b678627c76a6360fc40dc109aa7dea371b825e46134f624572182acf3957e70ffd1fac3d53313b11acd29f5a83ac11896dab2530fa47865b2295c0d99dd67c36ed8e5fa549150c794c5549efb5c1d69114d5d607b23285b7212afaab57846a54ae67b9e880e07b6586607cecf6d4eed516a3a75511fe367d88eb871e6d71b7d6aa1367a01421b1088fc2d75e44954b73625c52da8a3a183c60be9da6050f59a453caa53520593671728d431877bfaac913a765fb6a56b75290b2a8aaac34afb9217ba1b0d5850ba0fdabf80969def0feee794ceb60614e3368e63ef20e4c32d341ec9b0328ea9fe139207ed7a626ff08943b415233db7cfcc845c9b63121d4ed52ec3748ab6a1f36b2103c7dc7e9303acea4ba8af7a3e07184fb491e891ede84f0dc41cadc3973028e879acd2031afc29a16092868e2c7f539fc1b792edab195a25ab9830661346b39ef53915de4af52c421eaf172e9da76a08c283a52df907f705d7e8599c5baae0c2af380c1bb46f93484a03f28374324b278992b50b7afa02552cafa503f034f8d866e9b720271dd68ccb685a85fffd1:
|
||||||
|
79c7dcb7d59a8df6b2b2ba0413059d89680995c20e916da01b8f067dc60cdeb4298743c73918bd556b28f8d4824a09b814752a7aeae7ee04875c53f4d6b108d9:298743c73918bd556b28f8d4824a09b814752a7aeae7ee04875c53f4d6b108d9:5fe202f5b33b7788810d2508a13b3114d69b8596e6eacda05a04a2eb597fa3279c208b5a5b65daacb699f144e1d660e78e139b578331abec5c3c35334454f03e832c8d6e2984df5d450ecb5d33582a78808a9c78f26ebcd1244ef52e3fa6dca115c1f0cb56e38eae0e5b39f5fd863dffd0b2fb5b958f2d739db312fc667a17b031c4c9f8c5a2ad577984cc4146c437580efd2152173fe0d5782cc2ae9831a8d9a04177256018ff7631e0b0d8a99cb28f008b320421e27a74c31359188663456d85e098c1ebd281701097b6ae5a871e5ccc02058a501416cb91c12cef5be6f1914370e563f1a1b2aa41f4b8ee84cd32a1d509e529787d14a445438d807ecd620e2fa26de0da6426864784d4a28f54103e609283b99ee9b2b699c980bbb7882c3ea68ddc90802ac232f2c8e84291987bf3c5240921b59cfa214969317673d0be7f34b1ca0e15ea73c7175401ce550be106b49e62f8db68695e740e0f3a3556a19f3c8e6b91ac1cc23e863fcd0f0d9eb7047aa631e0d2eb9bcc6b:7b7cbe44c771e4371bae13b0722babcc1064155732962f407cba2acd35381d42210bece822f4681121fd4dab745a1f3077922fba1a78045b712902baccac660e5fe202f5b33b7788810d2508a13b3114d69b8596e6eacda05a04a2eb597fa3279c208b5a5b65daacb699f144e1d660e78e139b578331abec5c3c35334454f03e832c8d6e2984df5d450ecb5d33582a78808a9c78f26ebcd1244ef52e3fa6dca115c1f0cb56e38eae0e5b39f5fd863dffd0b2fb5b958f2d739db312fc667a17b031c4c9f8c5a2ad577984cc4146c437580efd2152173fe0d5782cc2ae9831a8d9a04177256018ff7631e0b0d8a99cb28f008b320421e27a74c31359188663456d85e098c1ebd281701097b6ae5a871e5ccc02058a501416cb91c12cef5be6f1914370e563f1a1b2aa41f4b8ee84cd32a1d509e529787d14a445438d807ecd620e2fa26de0da6426864784d4a28f54103e609283b99ee9b2b699c980bbb7882c3ea68ddc90802ac232f2c8e84291987bf3c5240921b59cfa214969317673d0be7f34b1ca0e15ea73c7175401ce550be106b49e62f8db68695e740e0f3a3556a19f3c8e6b91ac1cc23e863fcd0f0d9eb7047aa631e0d2eb9bcc6b:
|
||||||
|
b9ced0412593fefed95e94ac965e5b23ff9d4b0e797db02bf497994d3b793e60c1629a723189959337f5535201e5d395ba0a03ea8c17660d0f8b6f6e6404bb12:c1629a723189959337f5535201e5d395ba0a03ea8c17660d0f8b6f6e6404bb12:555bb39c1899d57cabe428064c2d925f5fc4cf7059b95fb89a8e9e3a7e426c6c922d9e4d76984ea2383cabb4f2befd89c1f20eaa8a00dbe787cfa70ae2ae6aa90331cbbe580fa5a02184ed05e6c8e89d576af28aeeaf7c4e2500f358a00971a0a75920e854849bf332142975404f598c32e96982043d992bcd1a4fe819bb5634ad03467afc4ce05073f88ba1ba4ae8653a04665cf3f71690fe13343885bc5ebc0e5e62d882f43b7c68900ac9438bf4a81ce90169ec129ee63e2c675a1a5a67e27cc798c48cc23f51078f463b3b7cc14e3bcfd2e9b82c75240934cbdc50c4308f282f193122995606f40135100a291c55afdf8934eb8b61d81421674124dec3b88f9a73110a9e616f5b826b9d343f3ac0e9d7bdf4fd8b648b40f0098b3897a3a1cd65a64570059b8bc5c6743883074c88623c1f5a88c58969e21c692aca236833d3470b3eb09815e1138e9d0650c390eee977422193b00918be8a97cc6199b451b05b5730d1d13358cf74610678f7ac7f7895cc2efc456e03873b:f1b797ded8a6942b12626848340fb719fcddafd98f33e2992d357bfdd35933c7ac561e5b2f939464338c5666854ca885c4d046eb2c54e48a1b5ed266ad34de05555bb39c1899d57cabe428064c2d925f5fc4cf7059b95fb89a8e9e3a7e426c6c922d9e4d76984ea2383cabb4f2befd89c1f20eaa8a00dbe787cfa70ae2ae6aa90331cbbe580fa5a02184ed05e6c8e89d576af28aeeaf7c4e2500f358a00971a0a75920e854849bf332142975404f598c32e96982043d992bcd1a4fe819bb5634ad03467afc4ce05073f88ba1ba4ae8653a04665cf3f71690fe13343885bc5ebc0e5e62d882f43b7c68900ac9438bf4a81ce90169ec129ee63e2c675a1a5a67e27cc798c48cc23f51078f463b3b7cc14e3bcfd2e9b82c75240934cbdc50c4308f282f193122995606f40135100a291c55afdf8934eb8b61d81421674124dec3b88f9a73110a9e616f5b826b9d343f3ac0e9d7bdf4fd8b648b40f0098b3897a3a1cd65a64570059b8bc5c6743883074c88623c1f5a88c58969e21c692aca236833d3470b3eb09815e1138e9d0650c390eee977422193b00918be8a97cc6199b451b05b5730d1d13358cf74610678f7ac7f7895cc2efc456e03873b:
|
||||||
|
81da168f02d46bb87cda845da43f8a6cba2c016878d6f49c6f061a60f155a04aaff86e98093ca4c71b1b804c5fe451cfdf868250dea30345fa4b89bb09b6a53b:aff86e98093ca4c71b1b804c5fe451cfdf868250dea30345fa4b89bb09b6a53b:6bc6726a34a64aae76ab08c92b179e54ff5d2e65eb2c6c659ae8703cc245cbc2cf45a12b22c468ae61fd9a6627ad0626c9b1e5af412cb483eaee1db11b29f0a510c13e38020e09ae0eee762537a3e9d1a0c7b033d097fdc1f4f82629a9de9ef38da1cf96a940357d5f2e0e7e8dbc29db728a1e6aad876e5e053113d06420272b87cf0c40dfe03a544de96c7aea13ba0029b57b48d99dcc6a650492d78c4cdd1b28e1a115a7e3e7a7cb21333d4ff80858dfb67782c16354b8716596560d7d8e389eb15a052a0bf5d16eb54fb3e4973ad4984e72a187f5347d5b262c32b1647e42b6a53837096cc78c2a05ce1c6e12493a03f1a667584cb97f4fcd57ee944c65b7eed25f7ae0f3f6cede173fdfacf5af1db143730d18096664914ba4cfc6966f392022781c66a9417ca2680b51f63e4fba424ecfdbc6a2f01787d0e7484f8a8ab390aeaa6d1f7ed325d82feaa1692a4984fae43da87329b045da8f0a4f56b695aa935de152ce0385153720979a2b7006d405fcb0fba09e23b85fd19b:4aaca947e3f22cc8b8588ee030ace8f6b5f5711c2974f20cc18c3b655b07a5bc1366b59a1708032d12cae01ab794f8cbcc1a330874a75035db1d69422d2fc00c6bc6726a34a64aae76ab08c92b179e54ff5d2e65eb2c6c659ae8703cc245cbc2cf45a12b22c468ae61fd9a6627ad0626c9b1e5af412cb483eaee1db11b29f0a510c13e38020e09ae0eee762537a3e9d1a0c7b033d097fdc1f4f82629a9de9ef38da1cf96a940357d5f2e0e7e8dbc29db728a1e6aad876e5e053113d06420272b87cf0c40dfe03a544de96c7aea13ba0029b57b48d99dcc6a650492d78c4cdd1b28e1a115a7e3e7a7cb21333d4ff80858dfb67782c16354b8716596560d7d8e389eb15a052a0bf5d16eb54fb3e4973ad4984e72a187f5347d5b262c32b1647e42b6a53837096cc78c2a05ce1c6e12493a03f1a667584cb97f4fcd57ee944c65b7eed25f7ae0f3f6cede173fdfacf5af1db143730d18096664914ba4cfc6966f392022781c66a9417ca2680b51f63e4fba424ecfdbc6a2f01787d0e7484f8a8ab390aeaa6d1f7ed325d82feaa1692a4984fae43da87329b045da8f0a4f56b695aa935de152ce0385153720979a2b7006d405fcb0fba09e23b85fd19b:
|
||||||
|
af2e60da0f29bb1614fc3f193cc353331986b73f3f9a0aec9421b9473d6a4b6ac8bfe2835822199c6127b806fabeef0cb9ff59f3c81ff0cb89c556f55106af6a:c8bfe2835822199c6127b806fabeef0cb9ff59f3c81ff0cb89c556f55106af6a:7dbb77b88bda94f344416a06b096566c6e8b393931a8243a6cab75c361fde7dc536aec40cded83296a89e8c3bef7d787cfc49401a7b9183f138d5000619ff073c05e2f841d6008358f10a2da7dcfac3d4d70c20d2ec34c7b6d5cd1a734d6bbb11c5fd8d2bce32ac810ef82b4188aa8ea3cfc3032233dc0e2600e9db6e18bc22b10044a31c15baceaf5554de89d2a3466807f244414d080ff2963956c6e83c8e144ed0066088b476ddcb564403447d9159f9089aba2b4d5575c4d8ae66fc8690e7349ed40832e6369c024563ec493bfcc0fc9ac787ac841397fe133167283d80c42f006a99d39e82979da3fa9334bd9ede0d14b41b7466bcebbe8171bc804a645d3723274a1b92bf82fd993358744de92441903d436fd47f23d40052a3829367f202f0553b5e49b76c5e03fa6ce7c3cf5eeb21de967bec4dd355925384ebf96697e823762bac4d43a767c241a4cef724a970d00ff3a8ab3b83eed840075c74e90f306e330013260962161e9d0910de183622ce9a6b8d5144280550fc7:50f9f941a8da9f6240f76d2fa3b06dd6b2292ed32d1c05218097d34d8a19dfe553f76ae3c6b4a2ed20852128461540decf418f52d38e64037eec7771bd1afe007dbb77b88bda94f344416a06b096566c6e8b393931a8243a6cab75c361fde7dc536aec40cded83296a89e8c3bef7d787cfc49401a7b9183f138d5000619ff073c05e2f841d6008358f10a2da7dcfac3d4d70c20d2ec34c7b6d5cd1a734d6bbb11c5fd8d2bce32ac810ef82b4188aa8ea3cfc3032233dc0e2600e9db6e18bc22b10044a31c15baceaf5554de89d2a3466807f244414d080ff2963956c6e83c8e144ed0066088b476ddcb564403447d9159f9089aba2b4d5575c4d8ae66fc8690e7349ed40832e6369c024563ec493bfcc0fc9ac787ac841397fe133167283d80c42f006a99d39e82979da3fa9334bd9ede0d14b41b7466bcebbe8171bc804a645d3723274a1b92bf82fd993358744de92441903d436fd47f23d40052a3829367f202f0553b5e49b76c5e03fa6ce7c3cf5eeb21de967bec4dd355925384ebf96697e823762bac4d43a767c241a4cef724a970d00ff3a8ab3b83eed840075c74e90f306e330013260962161e9d0910de183622ce9a6b8d5144280550fc7:
|
||||||
|
605f90b53d8e4a3b48b97d745439f2a0807d83b8502e8e2979f03e8d376ac9feaa3fae4cfa6f6bfd14ba0afa36dcb1a2656f36541ad6b3e67f1794b06360a62f:aa3fae4cfa6f6bfd14ba0afa36dcb1a2656f36541ad6b3e67f1794b06360a62f:3bcdcac292ac9519024aaecee2b3e999ff5d3445e9f1eb60940f06b91275b6c5db2722ed4d82fe89605226530f3e6b0737b308cde8956184944f388a80042f6cba274c0f7d1192a0a96b0da6e2d6a61b76518fbee555773a414590a928b4cd545fccf58172f35857120eb96e75c5c8ac9ae3add367d51d34ac403446360ec10f553ea9f14fb2b8b78cba18c3e506b2f04097063a43b2d36431cce02caf11c5a4db8c821752e52985d5af1bfbf4c61572e3fadae3ad424acd81662ea5837a1143b9669391d7b9cfe230cffb3a7bb03f6591c25a4f01c0d2d4aca3e74db1997d3739c851f0327db919ff6e77f6c8a20fdd3e1594e92d01901ab9aef194fc893e70d78c8ae0f480001a515d4f9923ae6278e8927237d05db23e984c92a683882f57b1f1882a74a193ab6912ff241b9ffa662a0d47f29205f084dbde845baaeb5dd36ae6439a437642fa763b57e8dbe84e55813f0151e97e5b9de768b234b8db15c496d4bfcfa1388788972bb50ce030bc6e0ccf4fa7d00d343782f6ba8de0:dd0212e63288cbe14a4569b4d891da3c7f92727c5e7f9a801cf9d6827085e7095b669d7d45f882ca5f0745dccd24d87a57181320191e5b7a47c3f7f2dccbd7073bcdcac292ac9519024aaecee2b3e999ff5d3445e9f1eb60940f06b91275b6c5db2722ed4d82fe89605226530f3e6b0737b308cde8956184944f388a80042f6cba274c0f7d1192a0a96b0da6e2d6a61b76518fbee555773a414590a928b4cd545fccf58172f35857120eb96e75c5c8ac9ae3add367d51d34ac403446360ec10f553ea9f14fb2b8b78cba18c3e506b2f04097063a43b2d36431cce02caf11c5a4db8c821752e52985d5af1bfbf4c61572e3fadae3ad424acd81662ea5837a1143b9669391d7b9cfe230cffb3a7bb03f6591c25a4f01c0d2d4aca3e74db1997d3739c851f0327db919ff6e77f6c8a20fdd3e1594e92d01901ab9aef194fc893e70d78c8ae0f480001a515d4f9923ae6278e8927237d05db23e984c92a683882f57b1f1882a74a193ab6912ff241b9ffa662a0d47f29205f084dbde845baaeb5dd36ae6439a437642fa763b57e8dbe84e55813f0151e97e5b9de768b234b8db15c496d4bfcfa1388788972bb50ce030bc6e0ccf4fa7d00d343782f6ba8de0:
|
||||||
|
9e2c3d189838f4dd52ef0832886874c5ca493983ddadc07cbc570af2ee9d6209f68d3b81e73557ee1f08bd2d3f46a4718256a0f3cd8d2e03eb8fe882aab65c69:f68d3b81e73557ee1f08bd2d3f46a4718256a0f3cd8d2e03eb8fe882aab65c69:19485f5238ba82eadf5eff14ca75cd42e5d56fea69d5718cfb5b1d40d760899b450e66884558f3f25b7c3de9afc4738d7ac09da5dd4689bbfac07836f5e0be432b1ddcf1b1a075bc9815d0debc865d90bd5a0c5f5604d9b46ace816c57694ecc3d40d8f84df0ede2bc4d577775a027f725de0816f563fa88f88e077720ebb6ac02574604819824db7474d4d0b22cd1bc05768e0fb867ca1c1a7b90b34ab7a41afc66957266ac0c915934aaf31c0cf6927a4f03f23285e6f24afd5813849bb08c203ac2d0336dcbf80d77f6cf7120edfbcdf181db107ec8e00f32449c1d3f5c049a92694b4ea2c6ebe5e2b0f64b5ae50ad3374d246b3270057e724a27cf263b633ab65ecb7f5c266b8007618b10ac9ac83db0febc04fd863d9661ab6e58494766f71b9a867c5a7a4555f667c1af2e54588f162a41ce756407cc4161d607b6e0682980934caa1bef036f7330d9eef01ecc553583fee5994e533a46ca916f60f8b961ae01d20f7abf0df6141b604de733c636b42018cd5f1d1ef4f84cee40fc:38a31b6b465084738262a26c065fe5d9e2886bf9dd35cde05df9bad0cc7db401c750aa19e66090bce25a3c721201e60502c8c10454346648af065eab0ee7d80f19485f5238ba82eadf5eff14ca75cd42e5d56fea69d5718cfb5b1d40d760899b450e66884558f3f25b7c3de9afc4738d7ac09da5dd4689bbfac07836f5e0be432b1ddcf1b1a075bc9815d0debc865d90bd5a0c5f5604d9b46ace816c57694ecc3d40d8f84df0ede2bc4d577775a027f725de0816f563fa88f88e077720ebb6ac02574604819824db7474d4d0b22cd1bc05768e0fb867ca1c1a7b90b34ab7a41afc66957266ac0c915934aaf31c0cf6927a4f03f23285e6f24afd5813849bb08c203ac2d0336dcbf80d77f6cf7120edfbcdf181db107ec8e00f32449c1d3f5c049a92694b4ea2c6ebe5e2b0f64b5ae50ad3374d246b3270057e724a27cf263b633ab65ecb7f5c266b8007618b10ac9ac83db0febc04fd863d9661ab6e58494766f71b9a867c5a7a4555f667c1af2e54588f162a41ce756407cc4161d607b6e0682980934caa1bef036f7330d9eef01ecc553583fee5994e533a46ca916f60f8b961ae01d20f7abf0df6141b604de733c636b42018cd5f1d1ef4f84cee40fc:
|
||||||
|
575f8fb6c7465e92c250caeec1786224bc3eed729e463953a394c9849cba908f71bfa98f5bea790ff183d924e6655cea08d0aafb617f46d23a17a657f0a9b8b2:71bfa98f5bea790ff183d924e6655cea08d0aafb617f46d23a17a657f0a9b8b2:2cc372e25e53a138793064610e7ef25d9d7422e18e249675a72e79167f43baf452cbacb50182faf80798cc38597a44b307a536360b0bc1030f8397b94cbf147353dd2d671cb8cab219a2d7b9eb828e9635d2eab6eb08182cb03557783fd282aaf7b471747c84acf72debe4514524f8447bafccccec0a840feca9755ff9adb60301c2f25d4e3ba621df5ad72100c45d7a4b91559c725ab56bb29830e35f5a6faf87db23001f11ffba9c0c15440302065827a7d7aaaeab7b446abce333c0d30c3eae9c9da63eb1c0391d4269b12c45b660290611ac29c91dbd80dc6ed302a4d191f2923922f032ab1ac10ca7323b5241c5751c3c004ac39eb1267aa10017ed2dac6c934a250dda8cb06d5be9f563b827bf3c8d95fd7d2a7e7cc3acbee92538bd7ddfba3ab2dc9f791fac76cdf9cd6a6923534cf3e067108f6aa03e320d954085c218038a70cc768b972e49952b9fe171ee1be2a52cd469b8d36b84ee902cd9410db2777192e90070d2e7c56cb6a45f0a839c78c219203b6f1b33cb4504c6a7996427741e6874cf45c5fa5a38765a1ebf1796ce16e63ee509612c40f088cbceffa3affbc13b75a1b9c02c61a180a7e83b17884fe0ec0f2fe57c47e73a22f753eaf50fca655ebb19896b827a3474911c67853c58b4a78fd085a23239b9737ef8a7baff11ddce5f2cae0543f8b45d144ae6918b9a75293ec78ea618cd2cd08c971301cdfa0a9275c1bf441d4c1f878a2e733ce0a33b6ecdacbbf0bdb5c3643fa45a013979cd01396962897421129a88757c0d88b5ac7e44fdbd938ba4bc37de4929d53751fbb43d4e09a80e735244acada8e6749f77787f33763c7472df52934591591fb226c503c8be61a920a7d37eb1686b62216957844c43c484e58745775553:903b484cb24bc503cdced844614073256c6d5aa45f1f9f62c7f22e5649212bc1d6ef9eaa617b6b835a6de2beff2faac83d37a4a5fc5cc3b556f56edde2651f022cc372e25e53a138793064610e7ef25d9d7422e18e249675a72e79167f43baf452cbacb50182faf80798cc38597a44b307a536360b0bc1030f8397b94cbf147353dd2d671cb8cab219a2d7b9eb828e9635d2eab6eb08182cb03557783fd282aaf7b471747c84acf72debe4514524f8447bafccccec0a840feca9755ff9adb60301c2f25d4e3ba621df5ad72100c45d7a4b91559c725ab56bb29830e35f5a6faf87db23001f11ffba9c0c15440302065827a7d7aaaeab7b446abce333c0d30c3eae9c9da63eb1c0391d4269b12c45b660290611ac29c91dbd80dc6ed302a4d191f2923922f032ab1ac10ca7323b5241c5751c3c004ac39eb1267aa10017ed2dac6c934a250dda8cb06d5be9f563b827bf3c8d95fd7d2a7e7cc3acbee92538bd7ddfba3ab2dc9f791fac76cdf9cd6a6923534cf3e067108f6aa03e320d954085c218038a70cc768b972e49952b9fe171ee1be2a52cd469b8d36b84ee902cd9410db2777192e90070d2e7c56cb6a45f0a839c78c219203b6f1b33cb4504c6a7996427741e6874cf45c5fa5a38765a1ebf1796ce16e63ee509612c40f088cbceffa3affbc13b75a1b9c02c61a180a7e83b17884fe0ec0f2fe57c47e73a22f753eaf50fca655ebb19896b827a3474911c67853c58b4a78fd085a23239b9737ef8a7baff11ddce5f2cae0543f8b45d144ae6918b9a75293ec78ea618cd2cd08c971301cdfa0a9275c1bf441d4c1f878a2e733ce0a33b6ecdacbbf0bdb5c3643fa45a013979cd01396962897421129a88757c0d88b5ac7e44fdbd938ba4bc37de4929d53751fbb43d4e09a80e735244acada8e6749f77787f33763c7472df52934591591fb226c503c8be61a920a7d37eb1686b62216957844c43c484e58745775553:
|
Loading…
Reference in New Issue