Use hashes instead of bitcoin_hashes
Use the more terse `hashes` by way of the `package` field in the manifest. Allows us to remove the ugly feature alias "bitcoin-hashes" -> "bitcoin_hashes" and removes all the bother with the underscore. Why did we not think of this 2 years ago?
This commit is contained in:
parent
70585395c3
commit
6d7c653b64
11
Cargo.toml
11
Cargo.toml
|
@ -21,8 +21,7 @@ default = ["std"]
|
||||||
std = ["alloc", "secp256k1-sys/std"]
|
std = ["alloc", "secp256k1-sys/std"]
|
||||||
# allow use of Secp256k1::new and related API that requires an allocator
|
# allow use of Secp256k1::new and related API that requires an allocator
|
||||||
alloc = ["secp256k1-sys/alloc"]
|
alloc = ["secp256k1-sys/alloc"]
|
||||||
bitcoin-hashes = ["bitcoin_hashes"] # Feature alias because of the underscore.
|
hashes-std = ["std", "hashes/std"]
|
||||||
bitcoin-hashes-std = ["std", "bitcoin_hashes", "bitcoin_hashes/std"]
|
|
||||||
rand-std = ["std", "rand", "rand/std", "rand/std_rng"]
|
rand-std = ["std", "rand", "rand/std", "rand/std_rng"]
|
||||||
recovery = ["secp256k1-sys/recovery"]
|
recovery = ["secp256k1-sys/recovery"]
|
||||||
lowmemory = ["secp256k1-sys/lowmemory"]
|
lowmemory = ["secp256k1-sys/lowmemory"]
|
||||||
|
@ -40,8 +39,8 @@ secp256k1-sys = { version = "0.8.1", default-features = false, path = "./secp256
|
||||||
serde = { version = "1.0.103", default-features = false, optional = true }
|
serde = { version = "1.0.103", default-features = false, optional = true }
|
||||||
|
|
||||||
# You likely only want to enable these if you explicitly do not want to use "std", otherwise enable
|
# You likely only want to enable these if you explicitly do not want to use "std", otherwise enable
|
||||||
# the respective -std feature e.g., bitcoin-hashes-std
|
# the respective -std feature e.g., hashes-std
|
||||||
bitcoin_hashes = { version = "0.12", default-features = false, optional = true }
|
hashes = { package = "bitcoin_hashes", version = "0.12", default-features = false, optional = true }
|
||||||
rand = { version = "0.8", default-features = false, optional = true }
|
rand = { version = "0.8", default-features = false, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -57,11 +56,11 @@ getrandom = { version = "0.2", features = ["js"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "sign_verify_recovery"
|
name = "sign_verify_recovery"
|
||||||
required-features = ["recovery", "bitcoin-hashes-std"]
|
required-features = ["recovery", "hashes-std"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "sign_verify"
|
name = "sign_verify"
|
||||||
required-features = ["bitcoin-hashes-std"]
|
required-features = ["hashes-std"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "generate_keys"
|
name = "generate_keys"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
REPO_DIR=$(git rev-parse --show-toplevel)
|
REPO_DIR=$(git rev-parse --show-toplevel)
|
||||||
FEATURES="bitcoin-hashes global-context lowmemory rand recovery serde std alloc bitcoin-hashes-std rand-std"
|
FEATURES="hashes global-context lowmemory rand recovery serde std alloc hashes-std rand-std"
|
||||||
|
|
||||||
cargo --version
|
cargo --version
|
||||||
rustc --version
|
rustc --version
|
||||||
|
@ -62,16 +62,16 @@ if [ "$DO_FEATURE_MATRIX" = true ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
cargo run --locked --example sign_verify --features=bitcoin-hashes-std
|
cargo run --locked --example sign_verify --features=hashes-std
|
||||||
cargo run --locked --example sign_verify_recovery --features=recovery,bitcoin-hashes-std
|
cargo run --locked --example sign_verify_recovery --features=recovery,hashes-std
|
||||||
cargo run --locked --example generate_keys --features=rand-std
|
cargo run --locked --example generate_keys --features=rand-std
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$DO_LINT" = true ]
|
if [ "$DO_LINT" = true ]
|
||||||
then
|
then
|
||||||
cargo clippy --locked --all-features --all-targets -- -D warnings
|
cargo clippy --locked --all-features --all-targets -- -D warnings
|
||||||
cargo clippy --locked --example sign_verify --features=bitcoin-hashes-std -- -D warnings
|
cargo clippy --locked --example sign_verify --features=hashes-std -- -D warnings
|
||||||
cargo clippy --locked --example sign_verify_recovery --features=recovery,bitcoin-hashes-std -- -D warnings
|
cargo clippy --locked --example sign_verify_recovery --features=recovery,hashes-std -- -D warnings
|
||||||
cargo clippy --locked --example generate_keys --features=rand-std -- -D warnings
|
cargo clippy --locked --example generate_keys --features=rand-std -- -D warnings
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
extern crate bitcoin_hashes;
|
extern crate hashes;
|
||||||
extern crate secp256k1;
|
extern crate secp256k1;
|
||||||
|
|
||||||
use bitcoin_hashes::{sha256, Hash};
|
use hashes::{sha256, Hash};
|
||||||
use secp256k1::{ecdsa, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification};
|
use secp256k1::{ecdsa, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification};
|
||||||
|
|
||||||
fn verify<C: Verification>(
|
fn verify<C: Verification>(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
extern crate bitcoin_hashes;
|
extern crate hashes;
|
||||||
extern crate secp256k1;
|
extern crate secp256k1;
|
||||||
|
|
||||||
use bitcoin_hashes::{sha256, Hash};
|
use hashes::{sha256, Hash};
|
||||||
use secp256k1::{ecdsa, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification};
|
use secp256k1::{ecdsa, Error, Message, PublicKey, Secp256k1, SecretKey, Signing, Verification};
|
||||||
|
|
||||||
fn recover<C: Verification>(
|
fn recover<C: Verification>(
|
||||||
|
|
|
@ -110,7 +110,7 @@ impl AsRef<[u8]> for SharedSecret {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(all(feature = "bitcoin-hashes-std", feature = "rand-std"))] {
|
/// # #[cfg(all(feature = "hashes-std", feature = "rand-std"))] {
|
||||||
/// # use secp256k1::{ecdh, rand, Secp256k1, PublicKey, SecretKey};
|
/// # use secp256k1::{ecdh, rand, Secp256k1, PublicKey, SecretKey};
|
||||||
/// # use secp256k1::hashes::{Hash, sha512};
|
/// # use secp256k1::hashes::{Hash, sha512};
|
||||||
///
|
///
|
||||||
|
@ -225,9 +225,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(secp256k1_fuzz))]
|
#[cfg(not(secp256k1_fuzz))]
|
||||||
#[cfg(all(feature = "bitcoin-hashes-std", feature = "rand-std"))]
|
#[cfg(all(feature = "hashes-std", feature = "rand-std"))]
|
||||||
fn bitcoin_hashes_and_sys_generate_same_secret() {
|
fn hashes_and_sys_generate_same_secret() {
|
||||||
use bitcoin_hashes::{sha256, Hash, HashEngine};
|
use hashes::{sha256, Hash, HashEngine};
|
||||||
|
|
||||||
use crate::ecdh::shared_secret_point;
|
use crate::ecdh::shared_secret_point;
|
||||||
|
|
||||||
|
|
10
src/key.rs
10
src/key.rs
|
@ -18,7 +18,7 @@ use crate::SECP256K1;
|
||||||
use crate::{
|
use crate::{
|
||||||
constants, ecdsa, from_hex, schnorr, Message, Scalar, Secp256k1, Signing, Verification,
|
constants, ecdsa, from_hex, schnorr, Message, Scalar, Secp256k1, Signing, Verification,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
use crate::{hashes, ThirtyTwoByteHash};
|
use crate::{hashes, ThirtyTwoByteHash};
|
||||||
|
|
||||||
/// Secret 256-bit key used as `x` in an ECDSA signature.
|
/// Secret 256-bit key used as `x` in an ECDSA signature.
|
||||||
|
@ -255,12 +255,12 @@ impl SecretKey {
|
||||||
|
|
||||||
/// Constructs a [`SecretKey`] by hashing `data` with hash algorithm `H`.
|
/// Constructs a [`SecretKey`] by hashing `data` with hash algorithm `H`.
|
||||||
///
|
///
|
||||||
/// Requires the feature `bitcoin_hashes` to be enabled.
|
/// Requires the feature `hashes` to be enabled.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(feature="bitcoin_hashes")] {
|
/// # #[cfg(feature="hashes")] {
|
||||||
/// use secp256k1::hashes::{sha256, Hash};
|
/// use secp256k1::hashes::{sha256, Hash};
|
||||||
/// use secp256k1::SecretKey;
|
/// use secp256k1::SecretKey;
|
||||||
///
|
///
|
||||||
|
@ -271,7 +271,7 @@ impl SecretKey {
|
||||||
/// assert_eq!(sk1, sk2);
|
/// assert_eq!(sk1, sk2);
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_hashed_data<H: ThirtyTwoByteHash + hashes::Hash>(data: &[u8]) -> Self {
|
pub fn from_hashed_data<H: ThirtyTwoByteHash + hashes::Hash>(data: &[u8]) -> Self {
|
||||||
<H as hashes::Hash>::hash(data).into()
|
<H as hashes::Hash>::hash(data).into()
|
||||||
|
@ -368,7 +368,7 @@ impl SecretKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
impl<T: ThirtyTwoByteHash> From<T> for SecretKey {
|
impl<T: ThirtyTwoByteHash> From<T> for SecretKey {
|
||||||
/// Converts a 32-byte hash directly to a secret key without error paths.
|
/// Converts a 32-byte hash directly to a secret key without error paths.
|
||||||
fn from(t: T) -> SecretKey {
|
fn from(t: T) -> SecretKey {
|
||||||
|
|
33
src/lib.rs
33
src/lib.rs
|
@ -28,7 +28,7 @@
|
||||||
//! trigger any assertion failures in the upstream library.
|
//! trigger any assertion failures in the upstream library.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[cfg(all(feature = "rand-std", feature = "bitcoin-hashes-std"))] {
|
//! # #[cfg(all(feature = "rand-std", feature = "hashes-std"))] {
|
||||||
//! use secp256k1::rand::rngs::OsRng;
|
//! use secp256k1::rand::rngs::OsRng;
|
||||||
//! use secp256k1::{Secp256k1, Message};
|
//! use secp256k1::{Secp256k1, Message};
|
||||||
//! use secp256k1::hashes::sha256;
|
//! use secp256k1::hashes::sha256;
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
//! If the "global-context" feature is enabled you have access to an alternate API.
|
//! If the "global-context" feature is enabled you have access to an alternate API.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[cfg(all(feature = "global-context", feature = "bitcoin-hashes-std", feature = "rand-std"))] {
|
//! # #[cfg(all(feature = "global-context", feature = "hashes-std", feature = "rand-std"))] {
|
||||||
//! use secp256k1::{generate_keypair, Message};
|
//! use secp256k1::{generate_keypair, Message};
|
||||||
//! use secp256k1::hashes::sha256;
|
//! use secp256k1::hashes::sha256;
|
||||||
//!
|
//!
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The above code requires `rust-secp256k1` to be compiled with the `rand-std` and `bitcoin-hashes-std`
|
//! The above code requires `rust-secp256k1` to be compiled with the `rand-std` and `hashes-std`
|
||||||
//! feature enabled, to get access to [`generate_keypair`](struct.Secp256k1.html#method.generate_keypair)
|
//! feature enabled, to get access to [`generate_keypair`](struct.Secp256k1.html#method.generate_keypair)
|
||||||
//! Alternately, keys and messages can be parsed from slices, like
|
//! Alternately, keys and messages can be parsed from slices, like
|
||||||
//!
|
//!
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
//! let secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order");
|
//! let secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order");
|
||||||
//! let public_key = PublicKey::from_secret_key(&secp, &secret_key);
|
//! let public_key = PublicKey::from_secret_key(&secp, &secret_key);
|
||||||
//! // This is unsafe unless the supplied byte slice is the output of a cryptographic hash function.
|
//! // This is unsafe unless the supplied byte slice is the output of a cryptographic hash function.
|
||||||
//! // See the above example for how to use this library together with `bitcoin-hashes-std`.
|
//! // See the above example for how to use this library together with `hashes-std`.
|
||||||
//! let message = Message::from_digest_slice(&[0xab; 32]).expect("32 bytes");
|
//! let message = Message::from_digest_slice(&[0xab; 32]).expect("32 bytes");
|
||||||
//!
|
//!
|
||||||
//! let sig = secp.sign_ecdsa(&message, &secret_key);
|
//! let sig = secp.sign_ecdsa(&message, &secret_key);
|
||||||
|
@ -127,8 +127,8 @@
|
||||||
//! * `alloc` - use the `alloc` standard Rust library to provide heap allocations.
|
//! * `alloc` - use the `alloc` standard Rust library to provide heap allocations.
|
||||||
//! * `rand` - use `rand` library to provide random generator (e.g. to generate keys).
|
//! * `rand` - use `rand` library to provide random generator (e.g. to generate keys).
|
||||||
//! * `rand-std` - use `rand` library with its `std` feature enabled. (Implies `rand`.)
|
//! * `rand-std` - use `rand` library with its `std` feature enabled. (Implies `rand`.)
|
||||||
//! * `bitcoin-hashes` - use the `bitcoin_hashes` library.
|
//! * `hashes` - use the `hashes` library.
|
||||||
//! * `bitcoin-hashes-std` - use the `bitcoin_hashes` library with its `std` feature enabled (implies `bitcoin-hashes`).
|
//! * `hashes-std` - use the `hashes` library with its `std` feature enabled (implies `hashes`).
|
||||||
//! * `recovery` - enable functions that can compute the public key from signature.
|
//! * `recovery` - enable functions that can compute the public key from signature.
|
||||||
//! * `lowmemory` - optimize the library for low-memory environments.
|
//! * `lowmemory` - optimize the library for low-memory environments.
|
||||||
//! * `global-context` - enable use of global secp256k1 context (implies `std`).
|
//! * `global-context` - enable use of global secp256k1 context (implies `std`).
|
||||||
|
@ -151,6 +151,9 @@ extern crate core;
|
||||||
#[cfg(bench)]
|
#[cfg(bench)]
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
#[cfg(feature = "hashes")]
|
||||||
|
pub extern crate hashes;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -170,8 +173,6 @@ use core::marker::PhantomData;
|
||||||
use core::ptr::NonNull;
|
use core::ptr::NonNull;
|
||||||
use core::{fmt, mem, str};
|
use core::{fmt, mem, str};
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
|
||||||
pub use bitcoin_hashes as hashes;
|
|
||||||
#[cfg(feature = "global-context")]
|
#[cfg(feature = "global-context")]
|
||||||
pub use context::global::SECP256K1;
|
pub use context::global::SECP256K1;
|
||||||
#[cfg(feature = "rand")]
|
#[cfg(feature = "rand")]
|
||||||
|
@ -183,7 +184,7 @@ pub use serde;
|
||||||
pub use crate::context::*;
|
pub use crate::context::*;
|
||||||
use crate::ffi::types::AlignedType;
|
use crate::ffi::types::AlignedType;
|
||||||
use crate::ffi::CPtr;
|
use crate::ffi::CPtr;
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
use crate::hashes::Hash;
|
use crate::hashes::Hash;
|
||||||
pub use crate::key::{PublicKey, SecretKey, *};
|
pub use crate::key::{PublicKey, SecretKey, *};
|
||||||
pub use crate::scalar::Scalar;
|
pub use crate::scalar::Scalar;
|
||||||
|
@ -196,17 +197,17 @@ pub trait ThirtyTwoByteHash {
|
||||||
fn into_32(self) -> [u8; 32];
|
fn into_32(self) -> [u8; 32];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
impl ThirtyTwoByteHash for hashes::sha256::Hash {
|
impl ThirtyTwoByteHash for hashes::sha256::Hash {
|
||||||
fn into_32(self) -> [u8; 32] { self.to_byte_array() }
|
fn into_32(self) -> [u8; 32] { self.to_byte_array() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
impl ThirtyTwoByteHash for hashes::sha256d::Hash {
|
impl ThirtyTwoByteHash for hashes::sha256d::Hash {
|
||||||
fn into_32(self) -> [u8; 32] { self.to_byte_array() }
|
fn into_32(self) -> [u8; 32] { self.to_byte_array() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
impl<T: hashes::sha256t::Tag> ThirtyTwoByteHash for hashes::sha256t::Hash<T> {
|
impl<T: hashes::sha256t::Tag> ThirtyTwoByteHash for hashes::sha256t::Hash<T> {
|
||||||
fn into_32(self) -> [u8; 32] { self.to_byte_array() }
|
fn into_32(self) -> [u8; 32] { self.to_byte_array() }
|
||||||
}
|
}
|
||||||
|
@ -268,12 +269,12 @@ impl Message {
|
||||||
|
|
||||||
/// Constructs a [`Message`] by hashing `data` with hash algorithm `H`.
|
/// Constructs a [`Message`] by hashing `data` with hash algorithm `H`.
|
||||||
///
|
///
|
||||||
/// Requires the feature `bitcoin-hashes` to be enabled.
|
/// Requires the feature `hashes` to be enabled.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(feature = "bitcoin_hashes")] {
|
/// # #[cfg(feature = "hashes")] {
|
||||||
/// use secp256k1::hashes::{sha256, Hash};
|
/// use secp256k1::hashes::{sha256, Hash};
|
||||||
/// use secp256k1::Message;
|
/// use secp256k1::Message;
|
||||||
///
|
///
|
||||||
|
@ -284,7 +285,7 @@ impl Message {
|
||||||
/// assert_eq!(m1, m2);
|
/// assert_eq!(m1, m2);
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
pub fn from_hashed_data<H: ThirtyTwoByteHash + hashes::Hash>(data: &[u8]) -> Self {
|
pub fn from_hashed_data<H: ThirtyTwoByteHash + hashes::Hash>(data: &[u8]) -> Self {
|
||||||
<H as hashes::Hash>::hash(data).into()
|
<H as hashes::Hash>::hash(data).into()
|
||||||
}
|
}
|
||||||
|
@ -1042,7 +1043,7 @@ mod tests {
|
||||||
assert!(SECP256K1.verify_ecdsa(&msg, &sig, &pk).is_ok());
|
assert!(SECP256K1.verify_ecdsa(&msg, &sig, &pk).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin_hashes")]
|
#[cfg(feature = "hashes")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_hash() {
|
fn test_from_hash() {
|
||||||
use crate::hashes::{self, Hash};
|
use crate::hashes::{self, Hash};
|
||||||
|
|
|
@ -32,7 +32,7 @@ macro_rules! impl_display_secret {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(not(feature = "std"), feature = "bitcoin_hashes"))]
|
#[cfg(all(not(feature = "std"), feature = "hashes"))]
|
||||||
impl ::core::fmt::Debug for $thing {
|
impl ::core::fmt::Debug for $thing {
|
||||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||||
use crate::hashes::{sha256, Hash, HashEngine};
|
use crate::hashes::{sha256, Hash, HashEngine};
|
||||||
|
@ -50,10 +50,10 @@ macro_rules! impl_display_secret {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(not(feature = "std"), not(feature = "bitcoin_hashes")))]
|
#[cfg(all(not(feature = "std"), not(feature = "hashes")))]
|
||||||
impl ::core::fmt::Debug for $thing {
|
impl ::core::fmt::Debug for $thing {
|
||||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||||
write!(f, "<secret requires std or bitcoin_hashes feature to display>")
|
write!(f, "<secret requires std or hashes feature to display>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue