Merge pull request #92 from huitseeker/rand-update
Bumps rand to 0.6.X & associated updates
This commit is contained in:
commit
7234606267
12
.travis.yml
12
.travis.yml
|
@ -5,18 +5,18 @@ rust:
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
- 1.14.0
|
- 1.22.0
|
||||||
os:
|
os:
|
||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
# rand 0.4 actually needs Rust 1.22, which leads to build failures on Rust 1.14 on Windows.
|
# rand 0.6 actually needs Rust 1.22, which leads to build failures on Rust 1.14 on Windows.
|
||||||
# This is not a problem, because
|
# This is a problem, because
|
||||||
# - we insist on rust 1.14 only for Debian, and
|
# - we insist on rust 1.22 since #92
|
||||||
# - "rand" is only an optional dependency.
|
# - but "rand" is only an optional dependency.
|
||||||
exclude:
|
exclude:
|
||||||
- rust: 1.14.0
|
- rust: 1.22.0
|
||||||
os: windows
|
os: windows
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
|
|
@ -31,11 +31,12 @@ default = []
|
||||||
fuzztarget = []
|
fuzztarget = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.4"
|
rand = "0.6"
|
||||||
|
rand_core = "0.3"
|
||||||
serde_test = "1.0"
|
serde_test = "1.0"
|
||||||
|
|
||||||
[dependencies.rand]
|
[dependencies.rand]
|
||||||
version = "0.4"
|
version = "0.6"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
|
|
|
@ -20,5 +20,4 @@ Contributions to this library are welcome. A few guidelines:
|
||||||
* Any breaking changes must have an accompanied entry in CHANGELOG.md
|
* Any breaking changes must have an accompanied entry in CHANGELOG.md
|
||||||
* No new dependencies, please.
|
* No new dependencies, please.
|
||||||
* No crypto should be implemented in Rust, with the possible exception of hash functions. Cryptographic contributions should be directed upstream to libsecp256k1.
|
* No crypto should be implemented in Rust, with the possible exception of hash functions. Cryptographic contributions should be directed upstream to libsecp256k1.
|
||||||
* This library should always compile with any combination of features on **Rust 1.14**, which is the currently shipping compiler on Debian.
|
* This library should always compile with any combination of features on **Rust 1.22**.
|
||||||
|
|
||||||
|
|
68
src/ffi.rs
68
src/ffi.rs
|
@ -21,13 +21,13 @@ use std::hash;
|
||||||
use std::os::raw::{c_int, c_uchar, c_uint, c_void};
|
use std::os::raw::{c_int, c_uchar, c_uint, c_void};
|
||||||
|
|
||||||
/// Flag for context to enable no precomputation
|
/// Flag for context to enable no precomputation
|
||||||
pub const SECP256K1_START_NONE: c_uint = (1 << 0) | 0;
|
pub const SECP256K1_START_NONE: c_uint = 1;
|
||||||
/// Flag for context to enable verification precomputation
|
/// Flag for context to enable verification precomputation
|
||||||
pub const SECP256K1_START_VERIFY: c_uint = (1 << 0) | (1 << 8);
|
pub const SECP256K1_START_VERIFY: c_uint = 1 | (1 << 8);
|
||||||
/// Flag for context to enable signing precomputation
|
/// Flag for context to enable signing precomputation
|
||||||
pub const SECP256K1_START_SIGN: c_uint = (1 << 0) | (1 << 9);
|
pub const SECP256K1_START_SIGN: c_uint = 1 | (1 << 9);
|
||||||
/// Flag for keys to indicate uncompressed serialization format
|
/// Flag for keys to indicate uncompressed serialization format
|
||||||
pub const SECP256K1_SER_UNCOMPRESSED: c_uint = (1 << 1) | 0;
|
pub const SECP256K1_SER_UNCOMPRESSED: c_uint = (1 << 1);
|
||||||
/// Flag for keys to indicate compressed serialization format
|
/// Flag for keys to indicate compressed serialization format
|
||||||
pub const SECP256K1_SER_COMPRESSED: c_uint = (1 << 1) | (1 << 8);
|
pub const SECP256K1_SER_COMPRESSED: c_uint = (1 << 1) | (1 << 8);
|
||||||
|
|
||||||
|
@ -74,6 +74,12 @@ impl PublicKey {
|
||||||
pub unsafe fn blank() -> PublicKey { mem::uninitialized() }
|
pub unsafe fn blank() -> PublicKey { mem::uninitialized() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PublicKey {
|
||||||
|
fn default() -> Self {
|
||||||
|
PublicKey::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl hash::Hash for PublicKey {
|
impl hash::Hash for PublicKey {
|
||||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||||
state.write(&self.0)
|
state.write(&self.0)
|
||||||
|
@ -99,6 +105,12 @@ impl Signature {
|
||||||
pub unsafe fn blank() -> Signature { mem::uninitialized() }
|
pub unsafe fn blank() -> Signature { mem::uninitialized() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Signature {
|
||||||
|
fn default() -> Self {
|
||||||
|
Signature::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RecoverableSignature {
|
impl RecoverableSignature {
|
||||||
/// Create a new (zeroed) signature usable for the FFI interface
|
/// Create a new (zeroed) signature usable for the FFI interface
|
||||||
pub fn new() -> RecoverableSignature { RecoverableSignature([0; 65]) }
|
pub fn new() -> RecoverableSignature { RecoverableSignature([0; 65]) }
|
||||||
|
@ -106,6 +118,12 @@ impl RecoverableSignature {
|
||||||
pub unsafe fn blank() -> RecoverableSignature { mem::uninitialized() }
|
pub unsafe fn blank() -> RecoverableSignature { mem::uninitialized() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RecoverableSignature {
|
||||||
|
fn default() -> Self {
|
||||||
|
RecoverableSignature::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Library-internal representation of an ECDH shared secret
|
/// Library-internal representation of an ECDH shared secret
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct SharedSecret([c_uchar; 32]);
|
pub struct SharedSecret([c_uchar; 32]);
|
||||||
|
@ -119,6 +137,12 @@ impl SharedSecret {
|
||||||
pub unsafe fn blank() -> SharedSecret { mem::uninitialized() }
|
pub unsafe fn blank() -> SharedSecret { mem::uninitialized() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SharedSecret {
|
||||||
|
fn default() -> Self {
|
||||||
|
SharedSecret::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "fuzztarget"))]
|
#[cfg(not(feature = "fuzztarget"))]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
/// Default ECDH hash function
|
/// Default ECDH hash function
|
||||||
|
@ -369,8 +393,8 @@ mod fuzz_dummy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signatures
|
// Signatures
|
||||||
pub unsafe fn secp256k1_ecdsa_signature_parse_der(cx: *const Context, sig: *mut Signature,
|
pub unsafe fn secp256k1_ecdsa_signature_parse_der(_cx: *const Context, _sig: *mut Signature,
|
||||||
input: *const c_uchar, in_len: usize)
|
_input: *const c_uchar, _in_len: usize)
|
||||||
-> c_int {
|
-> c_int {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
@ -385,8 +409,8 @@ mod fuzz_dummy {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn ecdsa_signature_parse_der_lax(cx: *const Context, sig: *mut Signature,
|
pub unsafe fn ecdsa_signature_parse_der_lax(_cx: *const Context, _sig: *mut Signature,
|
||||||
input: *const c_uchar, in_len: usize)
|
_input: *const c_uchar, _in_len: usize)
|
||||||
-> c_int {
|
-> c_int {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
@ -438,26 +462,26 @@ mod fuzz_dummy {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn secp256k1_ecdsa_recoverable_signature_parse_compact(cx: *const Context, sig: *mut RecoverableSignature,
|
pub unsafe fn secp256k1_ecdsa_recoverable_signature_parse_compact(_cx: *const Context, _sig: *mut RecoverableSignature,
|
||||||
input64: *const c_uchar, recid: c_int)
|
_input64: *const c_uchar, _recid: c_int)
|
||||||
-> c_int {
|
-> c_int {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn secp256k1_ecdsa_recoverable_signature_serialize_compact(cx: *const Context, output64: *const c_uchar,
|
pub unsafe fn secp256k1_ecdsa_recoverable_signature_serialize_compact(_cx: *const Context, _output64: *const c_uchar,
|
||||||
recid: *mut c_int, sig: *const RecoverableSignature)
|
_recid: *mut c_int, _sig: *const RecoverableSignature)
|
||||||
-> c_int {
|
-> c_int {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn secp256k1_ecdsa_recoverable_signature_convert(cx: *const Context, sig: *mut Signature,
|
pub unsafe fn secp256k1_ecdsa_recoverable_signature_convert(_cx: *const Context, _sig: *mut Signature,
|
||||||
input: *const RecoverableSignature)
|
_input: *const RecoverableSignature)
|
||||||
-> c_int {
|
-> c_int {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn secp256k1_ecdsa_signature_normalize(cx: *const Context, out_sig: *mut Signature,
|
pub unsafe fn secp256k1_ecdsa_signature_normalize(_cx: *const Context, _out_sig: *mut Signature,
|
||||||
in_sig: *const Signature)
|
_in_sig: *const Signature)
|
||||||
-> c_int {
|
-> c_int {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
@ -521,10 +545,10 @@ mod fuzz_dummy {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn secp256k1_ecdsa_recover(cx: *const Context,
|
pub unsafe fn secp256k1_ecdsa_recover(_cx: *const Context,
|
||||||
pk: *mut PublicKey,
|
_pk: *mut PublicKey,
|
||||||
sig: *const RecoverableSignature,
|
_sig: *const RecoverableSignature,
|
||||||
msg32: *const c_uchar)
|
_msg32: *const c_uchar)
|
||||||
-> c_int {
|
-> c_int {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
@ -640,8 +664,8 @@ mod fuzz_dummy {
|
||||||
out: *mut SharedSecret,
|
out: *mut SharedSecret,
|
||||||
point: *const PublicKey,
|
point: *const PublicKey,
|
||||||
scalar: *const c_uchar,
|
scalar: *const c_uchar,
|
||||||
hashfp: EcdhHashFn,
|
_hashfp: EcdhHashFn,
|
||||||
data: *mut c_void,
|
_data: *mut c_void,
|
||||||
) -> c_int {
|
) -> c_int {
|
||||||
assert!(!cx.is_null() && (*cx).0 as u32 & !(SECP256K1_START_NONE | SECP256K1_START_VERIFY | SECP256K1_START_SIGN) == 0);
|
assert!(!cx.is_null() && (*cx).0 as u32 & !(SECP256K1_START_NONE | SECP256K1_START_VERIFY | SECP256K1_START_SIGN) == 0);
|
||||||
if secp256k1_ec_seckey_verify(cx, scalar) != 1 { return 0; }
|
if secp256k1_ec_seckey_verify(cx, scalar) != 1 { return 0; }
|
||||||
|
|
41
src/key.rs
41
src/key.rs
|
@ -393,7 +393,8 @@ mod test {
|
||||||
use super::{PublicKey, SecretKey};
|
use super::{PublicKey, SecretKey};
|
||||||
use super::super::constants;
|
use super::super::constants;
|
||||||
|
|
||||||
use rand::{Rng, thread_rng};
|
use rand::{Error, ErrorKind, RngCore, thread_rng};
|
||||||
|
use rand_core::impls;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -462,8 +463,9 @@ mod test {
|
||||||
fn test_out_of_range() {
|
fn test_out_of_range() {
|
||||||
|
|
||||||
struct BadRng(u8);
|
struct BadRng(u8);
|
||||||
impl Rng for BadRng {
|
impl RngCore for BadRng {
|
||||||
fn next_u32(&mut self) -> u32 { unimplemented!() }
|
fn next_u32(&mut self) -> u32 { unimplemented!() }
|
||||||
|
fn next_u64(&mut self) -> u64 { unimplemented!() }
|
||||||
// This will set a secret key to a little over the
|
// This will set a secret key to a little over the
|
||||||
// group order, then decrement with repeated calls
|
// group order, then decrement with repeated calls
|
||||||
// until it returns a valid key
|
// until it returns a valid key
|
||||||
|
@ -478,6 +480,9 @@ mod test {
|
||||||
data[31] = self.0;
|
data[31] = self.0;
|
||||||
self.0 -= 1;
|
self.0 -= 1;
|
||||||
}
|
}
|
||||||
|
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
|
||||||
|
Ok(self.fill_bytes(dest))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = Secp256k1::new();
|
let s = Secp256k1::new();
|
||||||
|
@ -518,18 +523,28 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug_output() {
|
fn test_debug_output() {
|
||||||
struct DumbRng(u32);
|
struct DumbRng(u32);
|
||||||
impl Rng for DumbRng {
|
impl RngCore for DumbRng {
|
||||||
fn next_u32(&mut self) -> u32 {
|
fn next_u32(&mut self) -> u32 {
|
||||||
self.0 = self.0.wrapping_add(1);
|
self.0 = self.0.wrapping_add(1);
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
fn next_u64(&mut self) -> u64 {
|
||||||
|
self.next_u32() as u64
|
||||||
|
}
|
||||||
|
fn try_fill_bytes(&mut self, _dest: &mut [u8]) -> Result<(), Error> {
|
||||||
|
Err(Error::new(ErrorKind::Unavailable, "not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||||
|
impls::fill_bytes_via_next(self, dest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = Secp256k1::new();
|
let s = Secp256k1::new();
|
||||||
let (sk, _) = s.generate_keypair(&mut DumbRng(0));
|
let (sk, _) = s.generate_keypair(&mut DumbRng(0));
|
||||||
|
|
||||||
assert_eq!(&format!("{:?}", sk),
|
assert_eq!(&format!("{:?}", sk),
|
||||||
"SecretKey(0200000001000000040000000300000006000000050000000800000007000000)");
|
"SecretKey(0100000000000000020000000000000003000000000000000400000000000000)");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -588,19 +603,29 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pubkey_serialize() {
|
fn test_pubkey_serialize() {
|
||||||
struct DumbRng(u32);
|
struct DumbRng(u32);
|
||||||
impl Rng for DumbRng {
|
impl RngCore for DumbRng {
|
||||||
fn next_u32(&mut self) -> u32 {
|
fn next_u32(&mut self) -> u32 {
|
||||||
self.0 = self.0.wrapping_add(1);
|
self.0 = self.0.wrapping_add(1);
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
fn next_u64(&mut self) -> u64 {
|
||||||
|
self.next_u32() as u64
|
||||||
|
}
|
||||||
|
fn try_fill_bytes(&mut self, _dest: &mut [u8]) -> Result<(), Error> {
|
||||||
|
Err(Error::new(ErrorKind::Unavailable, "not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||||
|
impls::fill_bytes_via_next(self, dest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = Secp256k1::new();
|
let s = Secp256k1::new();
|
||||||
let (_, pk1) = s.generate_keypair(&mut DumbRng(0));
|
let (_, pk1) = s.generate_keypair(&mut DumbRng(0));
|
||||||
assert_eq!(&pk1.serialize_uncompressed()[..],
|
assert_eq!(&pk1.serialize_uncompressed()[..],
|
||||||
&[4, 149, 16, 196, 140, 38, 92, 239, 179, 65, 59, 224, 230, 183, 91, 238, 240, 46, 186, 252, 175, 102, 52, 249, 98, 178, 123, 72, 50, 171, 196, 254, 236, 1, 189, 143, 242, 227, 16, 87, 247, 183, 162, 68, 237, 140, 92, 205, 151, 129, 166, 58, 111, 96, 123, 64, 180, 147, 51, 12, 209, 89, 236, 213, 206][..]);
|
&[4, 124, 121, 49, 14, 253, 63, 197, 50, 39, 194, 107, 17, 193, 219, 108, 154, 126, 9, 181, 248, 2, 12, 149, 233, 198, 71, 149, 134, 250, 184, 154, 229, 185, 28, 165, 110, 27, 3, 162, 126, 238, 167, 157, 242, 221, 76, 251, 237, 34, 231, 72, 39, 245, 3, 191, 64, 111, 170, 117, 103, 82, 28, 102, 163][..]);
|
||||||
assert_eq!(&pk1.serialize()[..],
|
assert_eq!(&pk1.serialize()[..],
|
||||||
&[2, 149, 16, 196, 140, 38, 92, 239, 179, 65, 59, 224, 230, 183, 91, 238, 240, 46, 186, 252, 175, 102, 52, 249, 98, 178, 123, 72, 50, 171, 196, 254, 236][..]);
|
&[3, 124, 121, 49, 14, 253, 63, 197, 50, 39, 194, 107, 17, 193, 219, 108, 154, 126, 9, 181, 248, 2, 12, 149, 233, 198, 71, 149, 134, 250, 184, 154, 229][..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -732,5 +757,3 @@ mod test {
|
||||||
assert_tokens(&pk, &[Token::BorrowedBytes(&PK_BYTES[..])]);
|
assert_tokens(&pk, &[Token::BorrowedBytes(&PK_BYTES[..])]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -136,6 +136,7 @@
|
||||||
#![cfg_attr(all(test, feature = "unstable"), feature(test))]
|
#![cfg_attr(all(test, feature = "unstable"), feature(test))]
|
||||||
#[cfg(all(test, feature = "unstable"))] extern crate test;
|
#[cfg(all(test, feature = "unstable"))] extern crate test;
|
||||||
#[cfg(any(test, feature = "rand"))] pub extern crate rand;
|
#[cfg(any(test, feature = "rand"))] pub extern crate rand;
|
||||||
|
#[cfg(any(test))] extern crate rand_core;
|
||||||
#[cfg(feature = "serde")] pub extern crate serde;
|
#[cfg(feature = "serde")] pub extern crate serde;
|
||||||
#[cfg(all(test, feature = "serde"))] extern crate serde_test;
|
#[cfg(all(test, feature = "serde"))] extern crate serde_test;
|
||||||
|
|
||||||
|
@ -222,7 +223,7 @@ pub fn from_i32(id: i32) -> Result<RecoveryId, Error> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Allows library users to convert recovery IDs to i32.
|
/// Allows library users to convert recovery IDs to i32.
|
||||||
pub fn to_i32(&self) -> i32 {
|
pub fn to_i32(self) -> i32 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -473,7 +474,7 @@ impl Message {
|
||||||
/// Converts a `MESSAGE_SIZE`-byte slice to a message object
|
/// Converts a `MESSAGE_SIZE`-byte slice to a message object
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_slice(data: &[u8]) -> Result<Message, Error> {
|
pub fn from_slice(data: &[u8]) -> Result<Message, Error> {
|
||||||
if data == &[0; constants::MESSAGE_SIZE] {
|
if data == [0; constants::MESSAGE_SIZE] {
|
||||||
return Err(Error::InvalidMessage);
|
return Err(Error::InvalidMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,6 +617,12 @@ impl Secp256k1<All> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Secp256k1<All> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Secp256k1<SignOnly> {
|
impl Secp256k1<SignOnly> {
|
||||||
/// Creates a new Secp256k1 context that can only be used for signing
|
/// Creates a new Secp256k1 context that can only be used for signing
|
||||||
pub fn signing_only() -> Secp256k1<SignOnly> {
|
pub fn signing_only() -> Secp256k1<SignOnly> {
|
||||||
|
@ -778,7 +785,7 @@ fn from_hex(hex: &str, target: &mut [u8]) -> Result<usize, ()> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use rand::{Rng, thread_rng};
|
use rand::{RngCore, thread_rng};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use key::{SecretKey, PublicKey};
|
use key::{SecretKey, PublicKey};
|
||||||
|
@ -1227,4 +1234,3 @@ mod benches {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue