diff --git a/Cargo.lock b/Cargo.lock
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -47,6 +47,12 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
 
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
 [[package]]
 name = "async-trait"
 version = "0.1.79"
@@ -158,6 +164,15 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
 
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "bindgen"
 version = "0.65.1"
@@ -179,6 +194,22 @@
  "syn 2.0.72",
 ]
 
+[[package]]
+name = "bitcoin-io"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56"
+
+[[package]]
+name = "bitcoin_hashes"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
+dependencies = [
+ "bitcoin-io",
+ "hex-conservative",
+]
+
 [[package]]
 name = "bitcoinsuite-core"
 version = "0.1.0"
@@ -480,6 +511,16 @@
  "unicode-width",
 ]
 
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "convert_case"
 version = "0.6.0"
@@ -585,13 +626,30 @@
 version = "0.1.0"
 dependencies = [
  "abc-rust-lint",
+ "ecash-secp256k1",
  "ripemd",
- "secp256k1-abc",
  "sha2",
  "thiserror",
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "ecash-secp256k1"
+version = "0.30.0"
+dependencies = [
+ "bincode",
+ "bitcoin_hashes",
+ "ecash-secp256k1-sys",
+ "getrandom",
+ "hex_lit",
+ "rand 0.8.5",
+ "rand_core 0.6.4",
+ "serde",
+ "serde_cbor",
+ "serde_test",
+ "wasm-bindgen-test",
+]
+
 [[package]]
 name = "ecash-secp256k1-sys"
 version = "0.10.0"
@@ -777,8 +835,10 @@
 checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
 dependencies = [
  "cfg-if",
+ "js-sys",
  "libc",
  "wasi",
+ "wasm-bindgen",
 ]
 
 [[package]]
@@ -793,6 +853,12 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
+[[package]]
+name = "half"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
+
 [[package]]
 name = "hash32"
 version = "0.2.1"
@@ -846,12 +912,27 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
+[[package]]
+name = "hex-conservative"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd"
+dependencies = [
+ "arrayvec",
+]
+
 [[package]]
 name = "hex-literal"
 version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
 
+[[package]]
+name = "hex_lit"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
+
 [[package]]
 name = "home"
 version = "0.5.9"
@@ -1007,6 +1088,15 @@
  "libc",
 ]
 
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -1604,6 +1694,12 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
 
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
 [[package]]
 name = "scopeguard"
 version = "1.2.0"
@@ -1622,22 +1718,6 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
 
-[[package]]
-name = "secp256k1-abc"
-version = "0.20.3"
-source = "git+https://github.com/raipay/secp256k1-abc?rev=b23e742#b23e74219f5c425eada0f53a52f9b51fdb3b23b2"
-dependencies = [
- "secp256k1-sys-abc",
-]
-
-[[package]]
-name = "secp256k1-sys-abc"
-version = "0.4.1"
-source = "git+https://github.com/raipay/secp256k1-abc?rev=b23e742#b23e74219f5c425eada0f53a52f9b51fdb3b23b2"
-dependencies = [
- "cc",
-]
-
 [[package]]
 name = "semver"
 version = "1.0.22"
@@ -1653,6 +1733,17 @@
  "serde_derive",
 ]
 
+[[package]]
+name = "serde_cbor"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7081ed758ec726a6ed8ee7e92f5d3f6e6f8c3901b1f972e3a4a2f2599fad14f"
+dependencies = [
+ "byteorder",
+ "half",
+ "serde",
+]
+
 [[package]]
 name = "serde_derive"
 version = "1.0.197"
@@ -1694,6 +1785,15 @@
  "serde",
 ]
 
+[[package]]
+name = "serde_test"
+version = "1.0.177"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde_urlencoded"
 version = "0.7.1"
@@ -2169,6 +2269,18 @@
  "wasm-bindgen-shared",
 ]
 
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
 [[package]]
 name = "wasm-bindgen-macro"
 version = "0.2.92"
@@ -2198,6 +2310,41 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 
+[[package]]
+name = "wasm-bindgen-test"
+version = "0.3.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b"
+dependencies = [
+ "console_error_panic_hook",
+ "js-sys",
+ "scoped-tls",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-bindgen-test-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-test-macro"
+version = "0.3.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "which"
 version = "4.4.2"
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@
     "chronik/chronik-proto",
     "chronik/chronik-util",
     "modules/ecash-lib-wasm",
+    "modules/ecash-secp256k1",
     "modules/ecash-secp256k1/ecash-secp256k1-sys",
 ]
 
diff --git a/modules/ecash-lib-wasm/Cargo.toml b/modules/ecash-lib-wasm/Cargo.toml
--- a/modules/ecash-lib-wasm/Cargo.toml
+++ b/modules/ecash-lib-wasm/Cargo.toml
@@ -26,9 +26,8 @@
 # Implementation of SHA-256 etc. cryptographic hash functions
 sha2 = "0.10"
 
-[dependencies.secp256k1-abc]
+[dependencies.ecash-secp256k1]
 # libsecp256k1 with support for BCH/XEC/XPI Schnorr signatures
-git = "https://github.com/raipay/secp256k1-abc"
-rev = "b23e742"
+path = "../ecash-secp256k1"
 default-features = false
 features = ["alloc"]
diff --git a/modules/ecash-lib-wasm/src/ecc.rs b/modules/ecash-lib-wasm/src/ecc.rs
--- a/modules/ecash-lib-wasm/src/ecc.rs
+++ b/modules/ecash-lib-wasm/src/ecc.rs
@@ -4,7 +4,7 @@
 
 //! Module for [`Ecc`] for signing secp256k1 signatures.
 
-use secp256k1_abc::{
+use ecash_secp256k1::{
     constants::SECRET_KEY_SIZE, All, Message, PublicKey, Secp256k1, SecretKey,
 };
 use thiserror::Error;
@@ -51,7 +51,9 @@
 }
 
 fn parse_msg(msg: &[u8]) -> Result<Message, String> {
-    Ok(Message::from_slice(msg).map_err(|_| InvalidMessageSize(msg.len()))?)
+    Ok(Message::from_digest(
+        msg.try_into().map_err(|_| InvalidMessageSize(msg.len()))?,
+    ))
 }
 
 #[wasm_bindgen]
@@ -82,7 +84,7 @@
     ) -> Result<Vec<u8>, String> {
         let seckey = parse_secret_key(seckey)?;
         let msg = parse_msg(msg)?;
-        let sig = self.curve.sign(&msg, &seckey);
+        let sig = self.curve.sign_ecdsa(&msg, &seckey);
         Ok(sig.serialize_der().to_vec())
     }
 
@@ -95,7 +97,7 @@
     ) -> Result<Vec<u8>, String> {
         let seckey = parse_secret_key(seckey)?;
         let msg = parse_msg(msg)?;
-        let sig = self.curve.schnorrabc_sign_no_aux_rand(&msg, &seckey);
+        let sig = self.curve.sign_schnorrabc_no_aux_rand(&msg, &seckey);
         Ok(sig.as_ref().to_vec())
     }
 }
diff --git a/modules/ecash-secp256k1/.gitignore b/modules/ecash-secp256k1/.gitignore
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/modules/ecash-secp256k1/Cargo.toml b/modules/ecash-secp256k1/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/Cargo.toml
@@ -0,0 +1,57 @@
+[package]
+name = "ecash-secp256k1"
+version = "0.30.0"
+authors = [
+    "Dawid Ciężarkiewicz <dpc@ucore.info>",
+    "Andrew Poelstra <apoelstra@wpsoftware.net>",
+    "Tobias Ruck <tobias.ruck@be.cash>",
+]
+license = "CC0-1.0"
+homepage = "https://github.com/rust-bitcoin/rust-secp256k1/"
+repository = "https://github.com/rust-bitcoin/rust-secp256k1/"
+documentation = "https://docs.rs/secp256k1/"
+description = "Rust wrapper library for Pieter Wuille's `libsecp256k1`. Implements ECDSA, eCash Schnorr and BIP 340 signatures for the SECG elliptic curve group secp256k1 and related utilities."
+keywords = [ "crypto", "ECDSA", "secp256k1", "libsecp256k1", "bitcoin" ]
+readme = "README.md"
+edition = "2021"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[features]
+default = ["std"]
+std = ["alloc", "secp256k1-sys/std", "rand?/std", "rand?/std_rng", "hashes?/std"]
+# allow use of Secp256k1::new and related API that requires an allocator
+alloc = ["secp256k1-sys/alloc"]
+recovery = ["secp256k1-sys/recovery"]
+lowmemory = ["secp256k1-sys/lowmemory"]
+global-context = ["std"]
+# disable re-randomization of the global context, which provides some
+# defense-in-depth against sidechannel attacks. You should only use
+# this feature if you expect the `rand` crate's thread_rng to panic.
+# (If you are sure the `rand` and `std` features will not be enabled, e.g.
+# if you are doing a no-std build, then this feature does nothing
+# and is not necessary.)
+global-context-less-secure = ["global-context"]
+
+[dependencies]
+secp256k1-sys = { version = "0.10.0", default-features = false, path = "./ecash-secp256k1-sys", package = "ecash-secp256k1-sys" }
+
+hashes = { package = "bitcoin_hashes", version = "0.14", default-features = false, optional = true }
+rand = { version = "0.8", default-features = false, optional = true }
+serde = { version = "1.0.103", default-features = false, optional = true }
+
+[dev-dependencies]
+rand_core = "0.6"
+serde_cbor = "0.10.0"
+serde_test = "1.0.19"
+bincode = "1.3.3"
+hex_lit = "0.1.1"
+
+[target.wasm32-unknown-unknown.dev-dependencies]
+wasm-bindgen-test = "0.3"
+getrandom = { version = "0.2", features = ["js"] }
+
+[lints.rust]
+unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(secp256k1_fuzz)', 'cfg(rust_secp_no_symbol_renaming)'] }
diff --git a/modules/ecash-secp256k1/LICENSE b/modules/ecash-secp256k1/LICENSE
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/LICENSE
@@ -0,0 +1,122 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
+
diff --git a/modules/ecash-secp256k1/README.md b/modules/ecash-secp256k1/README.md
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/README.md
@@ -0,0 +1,15 @@
+<div align="center">
+  <h1>Rust Secp256k1</h1>
+</div>
+
+`ecash-secp256k1` is a wrapper around [libsecp256k1](https://github.com/bitcoin-abc/secp256k1), a C
+library implementing various cryptographic functions using the [SECG](https://www.secg.org/) curve
+[secp256k1](https://en.bitcoin.it/wiki/Secp256k1).
+
+This library:
+
+-   exposes type-safe Rust bindings for all `libsecp256k1` functions
+-   implements key generation
+-   implements deterministic nonce generation via RFC6979
+-   implements many unit tests, adding to those already present in `libsecp256k1`
+-   makes no allocations (except in unit tests) for efficiency and use in freestanding implementations
diff --git a/modules/ecash-secp256k1/contrib/test.sh b/modules/ecash-secp256k1/contrib/test.sh
--- a/modules/ecash-secp256k1/contrib/test.sh
+++ b/modules/ecash-secp256k1/contrib/test.sh
@@ -15,6 +15,17 @@
 
 main() {
     ecash_secp256k1_sys
+    ecash_secp256k1
+}
+
+ecash_secp256k1() {
+    FEATURES_WITH_STD="hashes global-context global-context-less-secure lowmemory rand recovery serde"
+    FEATURES_WITHOUT_STD="hashes global-context global-context-less-secure lowmemory rand recovery serde alloc"
+
+    # Navigate to ecash-secp256k1-sys
+    pushd "$SCRIPT_DIR/../"
+    run_tests
+    popd
 }
 
 ecash_secp256k1_sys() {
@@ -23,8 +34,8 @@
 
     # Navigate to ecash-secp256k1-sys
     pushd "$SCRIPT_DIR/../ecash-secp256k1-sys"
-
     run_tests
+    popd
 }
 
 run_tests() {
@@ -48,8 +59,6 @@
 
     build_docs
     build_wasm
-
-    popd
 }
 
 # Build with each feature as well as all combinations of two features.
diff --git a/modules/ecash-secp256k1/ecash-secp256k1-sys/build.rs b/modules/ecash-secp256k1/ecash-secp256k1-sys/build.rs
--- a/modules/ecash-secp256k1/ecash-secp256k1-sys/build.rs
+++ b/modules/ecash-secp256k1/ecash-secp256k1-sys/build.rs
@@ -34,6 +34,7 @@
         .define("ENABLE_MODULE_SCHNORRSIG", Some("1"))
         .define("ENABLE_MODULE_SCHNORR", Some("1"))
         .define("ENABLE_MODULE_EXTRAKEYS", Some("1"))
+        .define("ENABLE_NO_ALLOC", Some("1"))
         // upstream sometimes introduces calls to printf, which we cannot
         // compile with WASM due to its lack of libc. printf is never
         // necessary and we can just #define it away.
diff --git a/modules/ecash-secp256k1/ecash-secp256k1-sys/src/lib.rs b/modules/ecash-secp256k1/ecash-secp256k1-sys/src/lib.rs
--- a/modules/ecash-secp256k1/ecash-secp256k1-sys/src/lib.rs
+++ b/modules/ecash-secp256k1/ecash-secp256k1-sys/src/lib.rs
@@ -1145,11 +1145,14 @@
     use crate::*;
 
     extern "C" {
+        #[link_name = "secp256k1_context_preallocated_size"]
         fn ecash_secp256k1_context_preallocated_size(flags: c_uint) -> size_t;
+        #[link_name = "secp256k1_context_preallocated_create"]
         fn ecash_secp256k1_context_preallocated_create(
             prealloc: NonNull<c_void>,
             flags: c_uint,
         ) -> NonNull<Context>;
+        #[link_name = "secp256k1_context_preallocated_clone"]
         fn ecash_secp256k1_context_preallocated_clone(
             cx: *const Context,
             prealloc: NonNull<c_void>,
@@ -1603,6 +1606,49 @@
         secp256k1_schnorrsig_sign(cx, sig, msg, keypair, ptr::null())
     }
 
+    /// Schnorr ABC Signatures:
+    /// Sets sig to msghash32||pk[..32]
+    pub unsafe fn secp256k1_schnorr_sign(
+        cx: *const Context,
+        sig64: *mut c_uchar,
+        msghash32: *const c_uchar,
+        seckey: *const c_uchar,
+        _noncefn: NonceFn,
+        _noncedata: *const c_void,
+    ) -> c_int {
+        check_context_flags(cx, SECP256K1_START_SIGN);
+        // Check context is built for signing (and compute pk)
+        let mut new_pk = PublicKey::new();
+        if secp256k1_ec_pubkey_create(cx, &mut new_pk, seckey) != 1 {
+            return 0;
+        }
+        // Sign
+        let sig_sl = slice::from_raw_parts_mut(sig64 as *mut u8, 64);
+        let msg_sl = slice::from_raw_parts(msghash32 as *const u8, 32);
+        sig_sl[..32].copy_from_slice(msg_sl);
+        sig_sl[32..].copy_from_slice(&new_pk.0[..32]);
+        1
+    }
+
+    /// Schnorr ABC Signatures:
+    /// Verifies that sig is msghash32||pk[32..]
+    pub unsafe fn secp256k1_schnorr_verify(
+        cx: *const Context,
+        sig64: *const c_uchar,
+        msghash32: *const c_uchar,
+        pubkey: *const PublicKey,
+    ) -> c_int {
+        check_context_flags(cx, SECP256K1_START_VERIFY);
+        // Actually verify
+        let sig_sl = slice::from_raw_parts(sig64 as *const u8, 64);
+        let msg_sl = slice::from_raw_parts(msghash32 as *const u8, 32);
+        if &sig_sl[..32] == msg_sl && sig_sl[32..] == (*pubkey).0[0..32] {
+            1
+        } else {
+            0
+        }
+    }
+
     // Extra keys
     pub unsafe fn secp256k1_keypair_create(
         cx: *const Context,
diff --git a/modules/ecash-secp256k1/src/constants.rs b/modules/ecash-secp256k1/src/constants.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/constants.rs
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Constants related to the API and the underlying curve.
+
+/// The size (in bytes) of a message.
+pub const MESSAGE_SIZE: usize = 32;
+
+/// The size (in bytes) of a secret key.
+pub const SECRET_KEY_SIZE: usize = 32;
+
+/// The size (in bytes) of a serialized public key.
+pub const PUBLIC_KEY_SIZE: usize = 33;
+
+/// The size (in bytes) of an serialized uncompressed public key.
+pub const UNCOMPRESSED_PUBLIC_KEY_SIZE: usize = 65;
+
+/// The maximum size of a signature.
+pub const MAX_SIGNATURE_SIZE: usize = 72;
+
+/// The maximum size of a compact signature.
+pub const COMPACT_SIGNATURE_SIZE: usize = 64;
+
+/// The size of a schnorr signature.
+pub const SCHNORR_SIGNATURE_SIZE: usize = 64;
+
+/// The size of a schnorr public key.
+pub const SCHNORR_PUBLIC_KEY_SIZE: usize = 32;
+
+/// The size of a key pair.
+pub const KEY_PAIR_SIZE: usize = 96;
+
+/// The size of a full ElligatorSwift encoding.
+pub const ELLSWIFT_ENCODING_SIZE: usize = 64;
+
+/// The Prime for the secp256k1 field element.
+#[rustfmt::skip]
+pub const FIELD_SIZE: [u8; 32] = [
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2f
+];
+
+/// The order of the secp256k1 curve.
+#[rustfmt::skip]
+pub const CURVE_ORDER: [u8; 32] = [
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+    0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
+    0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41
+];
+
+/// The X coordinate of the generator.
+#[rustfmt::skip]
+pub const GENERATOR_X: [u8; 32] = [
+    0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
+    0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
+    0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
+    0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98
+];
+
+/// The Y coordinate of the generator.
+#[rustfmt::skip]
+pub const GENERATOR_Y: [u8; 32] = [
+    0x48, 0x3a, 0xda, 0x77, 0x26, 0xa3, 0xc4, 0x65,
+    0x5d, 0xa4, 0xfb, 0xfc, 0x0e, 0x11, 0x08, 0xa8,
+    0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19,
+    0x9c, 0x47, 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8
+];
+
+/// The value zero as an array of bytes.
+pub const ZERO: [u8; 32] = [0; 32];
+
+/// The value one as big-endian array of bytes.
+pub const ONE: [u8; 32] = [
+    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, 1,
+];
diff --git a/modules/ecash-secp256k1/src/context.rs b/modules/ecash-secp256k1/src/context.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/context.rs
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: CC0-1.0
+
+use core::marker::PhantomData;
+use core::mem::ManuallyDrop;
+use core::ptr::NonNull;
+
+#[cfg(feature = "alloc")]
+pub use self::alloc_only::{All, SignOnly, VerifyOnly};
+use crate::ffi::types::{c_uint, c_void, AlignedType};
+use crate::ffi::{self, CPtr};
+use crate::{Error, Secp256k1};
+
+#[cfg(all(feature = "global-context", feature = "std"))]
+/// Module implementing a singleton pattern for a global `Secp256k1` context.
+pub mod global {
+
+    use std::ops::Deref;
+    use std::sync::Once;
+
+    use crate::{All, Secp256k1};
+
+    /// Proxy struct for global `SECP256K1` context.
+    #[derive(Debug, Copy, Clone)]
+    pub struct GlobalContext {
+        __private: (),
+    }
+
+    /// A global static context to avoid repeatedly creating contexts.
+    ///
+    /// If `rand` and `std` feature is enabled, context will have been
+    /// randomized using `thread_rng`.
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "global-context", feature = "rand",
+    /// #           feature = "std"))] {
+    /// use ecash_secp256k1::{PublicKey, SECP256K1};
+    /// let _ = SECP256K1.generate_keypair(&mut rand::thread_rng());
+    /// # }
+    /// ```
+    pub static SECP256K1: &GlobalContext = &GlobalContext { __private: () };
+
+    impl Deref for GlobalContext {
+        type Target = Secp256k1<All>;
+
+        #[allow(unused_mut)] // Unused when `rand` + `std` is not enabled.
+        fn deref(&self) -> &Self::Target {
+            static ONCE: Once = Once::new();
+            static mut CONTEXT: Option<Secp256k1<All>> = None;
+            ONCE.call_once(|| unsafe {
+                let mut ctx = Secp256k1::new();
+                #[cfg(all(
+                    not(target_arch = "wasm32"),
+                    feature = "rand",
+                    feature = "std",
+                    not(feature = "global-context-less-secure")
+                ))]
+                {
+                    ctx.randomize(&mut rand::thread_rng());
+                }
+                CONTEXT = Some(ctx);
+            });
+            unsafe { CONTEXT.as_ref().unwrap() }
+        }
+    }
+}
+
+/// A trait for all kinds of contexts that lets you define the exact flags and a
+/// function to deallocate memory. It isn't possible to implement this for types
+/// outside this crate.
+///
+/// # Safety
+///
+/// This trait is marked unsafe to allow unsafe implementations of `deallocate`.
+pub unsafe trait Context: private::Sealed {
+    /// Flags for the ffi.
+    const FLAGS: c_uint;
+    /// A constant description of the context.
+    const DESCRIPTION: &'static str;
+    /// A function to deallocate the memory when the context is dropped.
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must be valid. Further safety constraints may be imposed by
+    /// [`std::alloc::dealloc`].
+    unsafe fn deallocate(ptr: *mut u8, size: usize);
+}
+
+/// Marker trait for indicating that an instance of [`Secp256k1`] can be used
+/// for signing.
+pub trait Signing: Context {}
+
+/// Marker trait for indicating that an instance of [`Secp256k1`] can be used
+/// for verification.
+pub trait Verification: Context {}
+
+/// Represents the set of capabilities needed for signing (preallocated memory).
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct SignOnlyPreallocated<'buf> {
+    phantom: PhantomData<&'buf ()>,
+}
+
+/// Represents the set of capabilities needed for verification (preallocated
+/// memory).
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct VerifyOnlyPreallocated<'buf> {
+    phantom: PhantomData<&'buf ()>,
+}
+
+/// Represents the set of all capabilities (preallocated memory).
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct AllPreallocated<'buf> {
+    phantom: PhantomData<&'buf ()>,
+}
+
+mod private {
+    use super::*;
+    pub trait Sealed {}
+
+    impl<'buf> Sealed for AllPreallocated<'buf> {}
+    impl<'buf> Sealed for VerifyOnlyPreallocated<'buf> {}
+    impl<'buf> Sealed for SignOnlyPreallocated<'buf> {}
+}
+
+#[cfg(feature = "alloc")]
+mod alloc_only {
+    use core::marker::PhantomData;
+    use core::ptr::NonNull;
+
+    use super::private;
+    use crate::alloc::alloc;
+    use crate::ffi::types::{c_uint, c_void};
+    use crate::ffi::{self};
+    use crate::{AlignedType, Context, Secp256k1, Signing, Verification};
+
+    impl private::Sealed for SignOnly {}
+    impl private::Sealed for All {}
+    impl private::Sealed for VerifyOnly {}
+
+    const ALIGN_TO: usize = core::mem::align_of::<AlignedType>();
+
+    /// Represents the set of capabilities needed for signing.
+    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+    pub enum SignOnly {}
+
+    /// Represents the set of capabilities needed for verification.
+    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+    pub enum VerifyOnly {}
+
+    /// Represents the set of all capabilities.
+    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+    pub enum All {}
+
+    impl Signing for SignOnly {}
+    impl Signing for All {}
+
+    impl Verification for VerifyOnly {}
+    impl Verification for All {}
+
+    unsafe impl Context for SignOnly {
+        const DESCRIPTION: &'static str = "signing only";
+        const FLAGS: c_uint = ffi::SECP256K1_START_SIGN;
+
+        unsafe fn deallocate(ptr: *mut u8, size: usize) {
+            let layout =
+                alloc::Layout::from_size_align(size, ALIGN_TO).unwrap();
+            alloc::dealloc(ptr, layout);
+        }
+    }
+
+    unsafe impl Context for VerifyOnly {
+        const DESCRIPTION: &'static str = "verification only";
+        const FLAGS: c_uint = ffi::SECP256K1_START_VERIFY;
+
+        unsafe fn deallocate(ptr: *mut u8, size: usize) {
+            let layout =
+                alloc::Layout::from_size_align(size, ALIGN_TO).unwrap();
+            alloc::dealloc(ptr, layout);
+        }
+    }
+
+    unsafe impl Context for All {
+        const DESCRIPTION: &'static str = "all capabilities";
+        const FLAGS: c_uint = VerifyOnly::FLAGS | SignOnly::FLAGS;
+
+        unsafe fn deallocate(ptr: *mut u8, size: usize) {
+            let layout =
+                alloc::Layout::from_size_align(size, ALIGN_TO).unwrap();
+            alloc::dealloc(ptr, layout);
+        }
+    }
+
+    impl<C: Context> Secp256k1<C> {
+        /// Lets you create a context in a generic manner (sign/verify/all).
+        ///
+        /// If `rand` and `std` feature is enabled, context will have been
+        /// randomized using `thread_rng`.
+        /// If `rand` or `std` feature is not enabled please consider
+        /// randomizing the context as follows:
+        /// ```
+        /// # #[cfg(all(feature = "rand", feature = "std"))] {
+        /// # use ecash_secp256k1::Secp256k1;
+        /// # use ecash_secp256k1::rand::{thread_rng, RngCore};
+        /// let mut ctx = Secp256k1::new();
+        /// # let mut rng = thread_rng();
+        /// # let mut seed = [0u8; 32];
+        /// # rng.fill_bytes(&mut seed);
+        /// // let seed = <32 bytes of random data>
+        /// ctx.seeded_randomize(&seed);
+        /// # }
+        /// ```
+        #[cfg_attr(
+            not(all(feature = "rand", feature = "std")),
+            allow(clippy::let_and_return, unused_mut)
+        )]
+        pub fn gen_new() -> Secp256k1<C> {
+            #[cfg(target_arch = "wasm32")]
+            ffi::types::sanity_checks_for_wasm();
+
+            let size =
+                unsafe { ffi::secp256k1_context_preallocated_size(C::FLAGS) };
+            let layout =
+                alloc::Layout::from_size_align(size, ALIGN_TO).unwrap();
+            let ptr = unsafe { alloc::alloc(layout) };
+            let ptr = NonNull::new(ptr as *mut c_void)
+                .unwrap_or_else(|| alloc::handle_alloc_error(layout));
+
+            #[allow(unused_mut)]
+            // ctx is not mutated under some feature combinations.
+            let mut ctx: Secp256k1<C> = Secp256k1 {
+                ctx: unsafe {
+                    ffi::secp256k1_context_preallocated_create(ptr, C::FLAGS)
+                },
+                phantom: PhantomData,
+            };
+
+            #[cfg(all(
+                not(target_arch = "wasm32"),
+                feature = "rand",
+                feature = "std",
+                not(feature = "global-context-less-secure")
+            ))]
+            {
+                ctx.randomize(&mut rand::thread_rng());
+            }
+
+            #[allow(clippy::let_and_return)] // as for unusted_mut
+            ctx
+        }
+    }
+
+    impl Secp256k1<All> {
+        /// Creates a new Secp256k1 context with all capabilities.
+        ///
+        /// If `rand` and `std` feature is enabled, context will have been
+        /// randomized using `thread_rng`.
+        /// If `rand` or `std` feature is not enabled please consider
+        /// randomizing the context (see
+        /// docs for `Secp256k1::gen_new()`).
+        pub fn new() -> Secp256k1<All> {
+            Secp256k1::gen_new()
+        }
+    }
+
+    impl Secp256k1<SignOnly> {
+        /// Creates a new Secp256k1 context that can only be used for signing.
+        ///
+        /// If `rand` and `std` feature is enabled, context will have been
+        /// randomized using `thread_rng`.
+        /// If `rand` or `std` feature is not enabled please consider
+        /// randomizing the context (see
+        /// docs for `Secp256k1::gen_new()`).
+        pub fn signing_only() -> Secp256k1<SignOnly> {
+            Secp256k1::gen_new()
+        }
+    }
+
+    impl Secp256k1<VerifyOnly> {
+        /// Creates a new Secp256k1 context that can only be used for
+        /// verification.
+        ///
+        /// If `rand` and `std` feature is enabled, context will have been
+        /// randomized using `thread_rng`.
+        /// If `rand` or `std` feature is not enabled please consider
+        /// randomizing the context (see
+        /// docs for `Secp256k1::gen_new()`).
+        pub fn verification_only() -> Secp256k1<VerifyOnly> {
+            Secp256k1::gen_new()
+        }
+    }
+
+    impl Default for Secp256k1<All> {
+        fn default() -> Self {
+            Self::new()
+        }
+    }
+
+    impl<C: Context> Clone for Secp256k1<C> {
+        fn clone(&self) -> Secp256k1<C> {
+            let size = unsafe {
+                ffi::secp256k1_context_preallocated_clone_size(
+                    self.ctx.as_ptr(),
+                )
+            };
+            let layout =
+                alloc::Layout::from_size_align(size, ALIGN_TO).unwrap();
+            let ptr = unsafe { alloc::alloc(layout) };
+            let ptr = NonNull::new(ptr as *mut c_void)
+                .unwrap_or_else(|| alloc::handle_alloc_error(layout));
+
+            Secp256k1 {
+                ctx: unsafe {
+                    ffi::secp256k1_context_preallocated_clone(
+                        self.ctx.as_ptr(),
+                        ptr,
+                    )
+                },
+                phantom: PhantomData,
+            }
+        }
+    }
+}
+
+impl<'buf> Signing for SignOnlyPreallocated<'buf> {}
+impl<'buf> Signing for AllPreallocated<'buf> {}
+
+impl<'buf> Verification for VerifyOnlyPreallocated<'buf> {}
+impl<'buf> Verification for AllPreallocated<'buf> {}
+
+unsafe impl<'buf> Context for SignOnlyPreallocated<'buf> {
+    const DESCRIPTION: &'static str = "signing only";
+    const FLAGS: c_uint = ffi::SECP256K1_START_SIGN;
+
+    unsafe fn deallocate(_ptr: *mut u8, _size: usize) {
+        // Allocated by the user
+    }
+}
+
+unsafe impl<'buf> Context for VerifyOnlyPreallocated<'buf> {
+    const DESCRIPTION: &'static str = "verification only";
+    const FLAGS: c_uint = ffi::SECP256K1_START_VERIFY;
+
+    unsafe fn deallocate(_ptr: *mut u8, _size: usize) {
+        // Allocated by the user.
+    }
+}
+
+unsafe impl<'buf> Context for AllPreallocated<'buf> {
+    const DESCRIPTION: &'static str = "all capabilities";
+    const FLAGS: c_uint =
+        SignOnlyPreallocated::FLAGS | VerifyOnlyPreallocated::FLAGS;
+
+    unsafe fn deallocate(_ptr: *mut u8, _size: usize) {
+        // Allocated by the user.
+    }
+}
+
+/// Trait marking that a particular context object internally points to
+/// memory that must outlive `'a`
+///
+/// # Safety
+///
+/// This trait is used internally to gate which context markers can safely
+/// be used with the `preallocated_gen_new` function. Do not implement it
+/// on your own structures.
+pub unsafe trait PreallocatedContext<'a> {}
+
+unsafe impl<'buf> PreallocatedContext<'buf> for AllPreallocated<'buf> {}
+unsafe impl<'buf> PreallocatedContext<'buf> for SignOnlyPreallocated<'buf> {}
+unsafe impl<'buf> PreallocatedContext<'buf> for VerifyOnlyPreallocated<'buf> {}
+
+impl<'buf, C: Context + PreallocatedContext<'buf>> Secp256k1<C> {
+    /// Lets you create a context with a preallocated buffer in a generic manner
+    /// (sign/verify/all).
+    pub fn preallocated_gen_new(
+        buf: &'buf mut [AlignedType],
+    ) -> Result<Secp256k1<C>, Error> {
+        #[cfg(target_arch = "wasm32")]
+        ffi::types::sanity_checks_for_wasm();
+
+        if buf.len() < Self::preallocate_size_gen() {
+            return Err(Error::NotEnoughMemory);
+        }
+        // Safe because buf is not null since it is not empty.
+        let buf = unsafe {
+            NonNull::new_unchecked(buf.as_mut_c_ptr() as *mut c_void)
+        };
+
+        Ok(Secp256k1 {
+            ctx: unsafe {
+                ffi::secp256k1_context_preallocated_create(
+                    buf,
+                    AllPreallocated::FLAGS,
+                )
+            },
+            phantom: PhantomData,
+        })
+    }
+}
+
+impl<'buf> Secp256k1<AllPreallocated<'buf>> {
+    /// Creates a new Secp256k1 context with all capabilities.
+    pub fn preallocated_new(
+        buf: &'buf mut [AlignedType],
+    ) -> Result<Secp256k1<AllPreallocated<'buf>>, Error> {
+        Secp256k1::preallocated_gen_new(buf)
+    }
+
+    /// Uses the ffi `secp256k1_context_preallocated_size` to check the memory
+    /// size needed for a context.
+    pub fn preallocate_size() -> usize {
+        Self::preallocate_size_gen()
+    }
+
+    /// Creates a context from a raw context.
+    ///
+    /// The returned [`core::mem::ManuallyDrop`] context will never deallocate
+    /// the memory pointed to by `raw_ctx` nor destroy the context. This may
+    /// lead to memory leaks. `ManuallyDrop::drop`
+    /// (or [`core::ptr::drop_in_place`]) will only destroy the context; the
+    /// caller is required to free the memory.
+    ///
+    /// # Safety
+    ///
+    /// This is highly unsafe due to a number of conditions that aren't checked,
+    /// specifically:
+    ///
+    /// * `raw_ctx` must be a valid pointer (live, aligned...) to memory that
+    ///   was initialized by `secp256k1_context_preallocated_create` (either
+    ///   called directly or from this library by one of the context creation
+    ///   methods - all of which call it internally).
+    /// * The version of `libsecp256k1` used to create `raw_ctx` must be
+    ///   **exactly the one linked into this library**.
+    /// * The lifetime of the `raw_ctx` pointer must outlive `'buf`.
+    /// * `raw_ctx` must point to writable memory (cannot be
+    ///   `ffi::secp256k1_context_no_precomp`).
+    pub unsafe fn from_raw_all(
+        raw_ctx: NonNull<ffi::Context>,
+    ) -> ManuallyDrop<Secp256k1<AllPreallocated<'buf>>> {
+        ManuallyDrop::new(Secp256k1 {
+            ctx: raw_ctx,
+            phantom: PhantomData,
+        })
+    }
+}
+
+impl<'buf> Secp256k1<SignOnlyPreallocated<'buf>> {
+    /// Creates a new Secp256k1 context that can only be used for signing.
+    pub fn preallocated_signing_only(
+        buf: &'buf mut [AlignedType],
+    ) -> Result<Secp256k1<SignOnlyPreallocated<'buf>>, Error> {
+        Secp256k1::preallocated_gen_new(buf)
+    }
+
+    /// Uses the ffi `secp256k1_context_preallocated_size` to check the memory
+    /// size needed for the context.
+    #[inline]
+    pub fn preallocate_signing_size() -> usize {
+        Self::preallocate_size_gen()
+    }
+
+    /// Creates a context from a raw context that can only be used for signing.
+    ///
+    /// # Safety
+    ///
+    /// Please see [`Secp256k1::from_raw_all`] for full documentation and safety
+    /// requirements.
+    pub unsafe fn from_raw_signing_only(
+        raw_ctx: NonNull<ffi::Context>,
+    ) -> ManuallyDrop<Secp256k1<SignOnlyPreallocated<'buf>>> {
+        ManuallyDrop::new(Secp256k1 {
+            ctx: raw_ctx,
+            phantom: PhantomData,
+        })
+    }
+}
+
+impl<'buf> Secp256k1<VerifyOnlyPreallocated<'buf>> {
+    /// Creates a new Secp256k1 context that can only be used for verification
+    pub fn preallocated_verification_only(
+        buf: &'buf mut [AlignedType],
+    ) -> Result<Secp256k1<VerifyOnlyPreallocated<'buf>>, Error> {
+        Secp256k1::preallocated_gen_new(buf)
+    }
+
+    /// Uses the ffi `secp256k1_context_preallocated_size` to check the memory
+    /// size needed for the context.
+    #[inline]
+    pub fn preallocate_verification_size() -> usize {
+        Self::preallocate_size_gen()
+    }
+
+    /// Creates a context from a raw context that can only be used for
+    /// verification.
+    ///
+    /// # Safety
+    ///
+    /// Please see [`Secp256k1::from_raw_all`] for full documentation and safety
+    /// requirements.
+    pub unsafe fn from_raw_verification_only(
+        raw_ctx: NonNull<ffi::Context>,
+    ) -> ManuallyDrop<Secp256k1<VerifyOnlyPreallocated<'buf>>> {
+        ManuallyDrop::new(Secp256k1 {
+            ctx: raw_ctx,
+            phantom: PhantomData,
+        })
+    }
+}
diff --git a/modules/ecash-secp256k1/src/ecdh.rs b/modules/ecash-secp256k1/src/ecdh.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/ecdh.rs
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Support for shared secret computations.
+
+use core::borrow::Borrow;
+use core::{ptr, str};
+
+use secp256k1_sys::types::{c_int, c_uchar, c_void};
+
+use crate::ffi::{self, CPtr};
+use crate::key::{PublicKey, SecretKey};
+use crate::{constants, Error};
+
+// The logic for displaying shared secrets relies on this (see `secret.rs`).
+const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE;
+
+/// Enables two parties to create a shared secret without revealing their own
+/// secrets.
+///
+/// # Examples
+///
+/// ```
+/// # #[cfg(all(feature = "rand", feature = "std"))] {
+/// # use ecash_secp256k1::{rand, Secp256k1};
+/// # use ecash_secp256k1::ecdh::SharedSecret;
+/// let s = Secp256k1::new();
+/// let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
+/// let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
+/// let sec1 = SharedSecret::new(&pk2, &sk1);
+/// let sec2 = SharedSecret::new(&pk1, &sk2);
+/// assert_eq!(sec1, sec2);
+/// # }
+// ```
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct SharedSecret([u8; SHARED_SECRET_SIZE]);
+impl_display_secret!(SharedSecret);
+impl_non_secure_erase!(SharedSecret, 0, [0u8; SHARED_SECRET_SIZE]);
+
+impl SharedSecret {
+    /// Creates a new shared secret from a pubkey and secret key.
+    #[inline]
+    pub fn new(point: &PublicKey, scalar: &SecretKey) -> SharedSecret {
+        let mut buf = [0u8; SHARED_SECRET_SIZE];
+        let res = unsafe {
+            ffi::secp256k1_ecdh(
+                ffi::secp256k1_context_no_precomp,
+                buf.as_mut_ptr(),
+                point.as_c_ptr(),
+                scalar.as_c_ptr(),
+                ffi::secp256k1_ecdh_hash_function_default,
+                ptr::null_mut(),
+            )
+        };
+        debug_assert_eq!(res, 1);
+        SharedSecret(buf)
+    }
+
+    /// Returns the shared secret as a byte value.
+    #[inline]
+    pub fn secret_bytes(&self) -> [u8; SHARED_SECRET_SIZE] {
+        self.0
+    }
+
+    /// Creates a shared secret from `bytes` array.
+    #[inline]
+    pub fn from_bytes(bytes: [u8; SHARED_SECRET_SIZE]) -> SharedSecret {
+        SharedSecret(bytes)
+    }
+
+    /// Creates a shared secret from `bytes` slice.
+    #[deprecated(since = "TBD", note = "Use `from_bytes` instead.")]
+    #[inline]
+    pub fn from_slice(bytes: &[u8]) -> Result<SharedSecret, Error> {
+        match bytes.len() {
+            SHARED_SECRET_SIZE => {
+                let mut ret = [0u8; SHARED_SECRET_SIZE];
+                ret[..].copy_from_slice(bytes);
+                Ok(SharedSecret(ret))
+            }
+            _ => Err(Error::InvalidSharedSecret),
+        }
+    }
+}
+
+impl str::FromStr for SharedSecret {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<SharedSecret, Error> {
+        let mut res = [0u8; SHARED_SECRET_SIZE];
+        match crate::from_hex(s, &mut res) {
+            Ok(SHARED_SECRET_SIZE) => Ok(SharedSecret::from_bytes(res)),
+            _ => Err(Error::InvalidSharedSecret),
+        }
+    }
+}
+
+impl Borrow<[u8]> for SharedSecret {
+    fn borrow(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl AsRef<[u8]> for SharedSecret {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+/// Creates a shared point from public key and secret key.
+///
+/// **Important: use of a strong cryptographic hash function may be critical to
+/// security! Do NOT use unless you understand cryptographical implications.**
+/// If not, use SharedSecret instead.
+///
+/// Can be used like `SharedSecret` but caller is responsible for then hashing
+/// the returned buffer. This allows for the use of a custom hash function since
+/// `SharedSecret` uses SHA256.
+///
+/// # Returns
+///
+/// 64 bytes representing the (x,y) co-ordinates of a point on the curve (32
+/// bytes each).
+///
+/// # Examples
+/// ```
+/// # #[cfg(all(feature = "hashes", feature = "rand", feature = "std"))] {
+/// # use ecash_secp256k1::{ecdh, rand, Secp256k1, PublicKey, SecretKey};
+/// # use ecash_secp256k1::hashes::{Hash, sha512};
+///
+/// let s = Secp256k1::new();
+/// let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
+/// let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
+///
+/// let point1 = ecdh::shared_secret_point(&pk2, &sk1);
+/// let secret1 = sha512::Hash::hash(&point1);
+/// let point2 = ecdh::shared_secret_point(&pk1, &sk2);
+/// let secret2 = sha512::Hash::hash(&point2);
+/// assert_eq!(secret1, secret2)
+/// # }
+/// ```
+pub fn shared_secret_point(point: &PublicKey, scalar: &SecretKey) -> [u8; 64] {
+    let mut xy = [0u8; 64];
+
+    let res = unsafe {
+        ffi::secp256k1_ecdh(
+            ffi::secp256k1_context_no_precomp,
+            xy.as_mut_ptr(),
+            point.as_c_ptr(),
+            scalar.as_c_ptr(),
+            Some(c_callback),
+            ptr::null_mut(),
+        )
+    };
+    // Our callback *always* returns 1.
+    // The scalar was verified to be valid (0 > scalar > group_order) via the
+    // type system.
+    debug_assert_eq!(res, 1);
+    xy
+}
+
+unsafe extern "C" fn c_callback(
+    output: *mut c_uchar,
+    x: *const c_uchar,
+    y: *const c_uchar,
+    _data: *mut c_void,
+) -> c_int {
+    ptr::copy_nonoverlapping(x, output, 32);
+    ptr::copy_nonoverlapping(y, output.offset(32), 32);
+    1
+}
+
+#[cfg(feature = "serde")]
+impl ::serde::Serialize for SharedSecret {
+    fn serialize<S: ::serde::Serializer>(
+        &self,
+        s: S,
+    ) -> Result<S::Ok, S::Error> {
+        if s.is_human_readable() {
+            let mut buf = [0u8; SHARED_SECRET_SIZE * 2];
+            s.serialize_str(
+                crate::to_hex(&self.0, &mut buf)
+                    .expect("fixed-size hex serialization"),
+            )
+        } else {
+            s.serialize_bytes(self.as_ref())
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> ::serde::Deserialize<'de> for SharedSecret {
+    fn deserialize<D: ::serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<Self, D::Error> {
+        if d.is_human_readable() {
+            d.deserialize_str(super::serde_util::FromStrVisitor::new(
+                "a hex string representing 32 byte SharedSecret",
+            ))
+        } else {
+            d.deserialize_bytes(super::serde_util::BytesVisitor::new(
+                "raw 32 bytes SharedSecret",
+                SharedSecret::from_slice,
+            ))
+        }
+    }
+}
+
+#[cfg(test)]
+#[allow(unused_imports)]
+mod tests {
+    #[cfg(target_arch = "wasm32")]
+    use wasm_bindgen_test::wasm_bindgen_test as test;
+
+    use super::SharedSecret;
+    use crate::Secp256k1;
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn ecdh() {
+        let s = Secp256k1::signing_only();
+        let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
+        let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
+
+        let sec1 = SharedSecret::new(&pk2, &sk1);
+        let sec2 = SharedSecret::new(&pk1, &sk2);
+        let sec_odd = SharedSecret::new(&pk1, &sk1);
+        assert_eq!(sec1, sec2);
+        assert!(sec_odd != sec2);
+    }
+
+    #[test]
+    fn test_c_callback() {
+        let x = [5u8; 32];
+        let y = [7u8; 32];
+        let mut output = [0u8; 64];
+        let res = unsafe {
+            super::c_callback(
+                output.as_mut_ptr(),
+                x.as_ptr(),
+                y.as_ptr(),
+                core::ptr::null_mut(),
+            )
+        };
+        assert_eq!(res, 1);
+        let mut new_x = [0u8; 32];
+        let mut new_y = [0u8; 32];
+        new_x.copy_from_slice(&output[..32]);
+        new_y.copy_from_slice(&output[32..]);
+        assert_eq!(x, new_x);
+        assert_eq!(y, new_y);
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))]
+    #[cfg(all(feature = "hashes", feature = "rand", feature = "std"))]
+    fn hashes_and_sys_generate_same_secret() {
+        use hashes::{sha256, Hash, HashEngine};
+
+        use crate::ecdh::shared_secret_point;
+
+        let s = Secp256k1::signing_only();
+        let (sk1, _) = s.generate_keypair(&mut rand::thread_rng());
+        let (_, pk2) = s.generate_keypair(&mut rand::thread_rng());
+
+        let secret_sys = SharedSecret::new(&pk2, &sk1);
+
+        let xy = shared_secret_point(&pk2, &sk1);
+
+        // Mimics logic in `bitcoin-core/secp256k1/src/module/main_impl.h`
+        let version = (xy[63] & 0x01) | 0x02;
+        let mut engine = sha256::HashEngine::default();
+        engine.input(&[version]);
+        engine.input(&xy.as_ref()[..32]);
+        let secret_bh = sha256::Hash::from_engine(engine);
+
+        assert_eq!(secret_bh.as_byte_array(), secret_sys.as_ref());
+    }
+
+    #[test]
+    #[cfg(all(feature = "serde", feature = "alloc"))]
+    fn serde() {
+        use serde_test::{assert_tokens, Configure, Token};
+        #[rustfmt::skip]
+        static BYTES: [u8; 32] = [
+            1, 1, 1, 1, 1, 1, 1, 1,
+            0, 1, 2, 3, 4, 5, 6, 7,
+            0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
+            99, 99, 99, 99, 99, 99, 99, 99
+        ];
+        static STR: &str =
+            "01010101010101010001020304050607ffff0000ffff00006363636363636363";
+
+        let secret = SharedSecret::from_slice(&BYTES).unwrap();
+
+        assert_tokens(&secret.compact(), &[Token::BorrowedBytes(&BYTES[..])]);
+        assert_tokens(&secret.compact(), &[Token::Bytes(&BYTES)]);
+        assert_tokens(&secret.compact(), &[Token::ByteBuf(&BYTES)]);
+
+        assert_tokens(&secret.readable(), &[Token::BorrowedStr(STR)]);
+        assert_tokens(&secret.readable(), &[Token::Str(STR)]);
+        assert_tokens(&secret.readable(), &[Token::String(STR)]);
+    }
+}
+
+#[cfg(bench)]
+// Currently only a single bench that requires "rand" + "std".
+#[cfg(all(feature = "rand", feature = "std"))]
+mod benches {
+    use test::{black_box, Bencher};
+
+    use super::SharedSecret;
+    use crate::Secp256k1;
+
+    #[bench]
+    pub fn bench_ecdh(bh: &mut Bencher) {
+        let s = Secp256k1::signing_only();
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+
+        bh.iter(|| {
+            let res = SharedSecret::new(&pk, &sk);
+            black_box(res);
+        });
+    }
+}
diff --git a/modules/ecash-secp256k1/src/ecdsa/mod.rs b/modules/ecash-secp256k1/src/ecdsa/mod.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/ecdsa/mod.rs
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Structs and functionality related to the ECDSA signature algorithm.
+
+#[cfg(feature = "recovery")]
+mod recovery;
+pub mod serialized_signature;
+
+use core::{fmt, ptr, str};
+
+#[cfg(feature = "recovery")]
+pub use self::recovery::{RecoverableSignature, RecoveryId};
+pub use self::serialized_signature::SerializedSignature;
+use crate::ffi::CPtr;
+#[cfg(feature = "global-context")]
+use crate::SECP256K1;
+use crate::{
+    ffi, from_hex, Error, Message, PublicKey, Secp256k1, SecretKey, Signing,
+    Verification,
+};
+
+/// An ECDSA signature
+#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct Signature(pub(crate) ffi::Signature);
+impl_fast_comparisons!(Signature);
+
+impl fmt::Debug for Signature {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
+
+impl fmt::Display for Signature {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let sig = self.serialize_der();
+        sig.fmt(f)
+    }
+}
+
+impl str::FromStr for Signature {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Signature, Error> {
+        let mut res = [0u8; 72];
+        match from_hex(s, &mut res) {
+            Ok(x) => Signature::from_der(&res[0..x]),
+            _ => Err(Error::InvalidSignature),
+        }
+    }
+}
+
+impl Signature {
+    #[inline]
+    /// Converts a DER-encoded byte slice to a signature
+    pub fn from_der(data: &[u8]) -> Result<Signature, Error> {
+        if data.is_empty() {
+            return Err(Error::InvalidSignature);
+        }
+
+        unsafe {
+            let mut ret = ffi::Signature::new();
+            if ffi::secp256k1_ecdsa_signature_parse_der(
+                ffi::secp256k1_context_no_precomp,
+                &mut ret,
+                data.as_c_ptr(),
+                data.len(),
+            ) == 1
+            {
+                Ok(Signature(ret))
+            } else {
+                Err(Error::InvalidSignature)
+            }
+        }
+    }
+
+    /// Converts a 64-byte compact-encoded byte slice to a signature
+    pub fn from_compact(data: &[u8]) -> Result<Signature, Error> {
+        if data.len() != 64 {
+            return Err(Error::InvalidSignature);
+        }
+
+        unsafe {
+            let mut ret = ffi::Signature::new();
+            if ffi::secp256k1_ecdsa_signature_parse_compact(
+                ffi::secp256k1_context_no_precomp,
+                &mut ret,
+                data.as_c_ptr(),
+            ) == 1
+            {
+                Ok(Signature(ret))
+            } else {
+                Err(Error::InvalidSignature)
+            }
+        }
+    }
+
+    /// Converts a "lax DER"-encoded byte slice to a signature. This is
+    /// basically only useful for validating signatures in the Bitcoin
+    /// blockchain from before 2016. It should never be used in new
+    /// applications. This library does not support serializing to this
+    /// "format"
+    pub fn from_der_lax(data: &[u8]) -> Result<Signature, Error> {
+        if data.is_empty() {
+            return Err(Error::InvalidSignature);
+        }
+
+        unsafe {
+            let mut ret = ffi::Signature::new();
+            if ffi::ecdsa_signature_parse_der_lax(
+                ffi::secp256k1_context_no_precomp,
+                &mut ret,
+                data.as_c_ptr(),
+                data.len(),
+            ) == 1
+            {
+                Ok(Signature(ret))
+            } else {
+                Err(Error::InvalidSignature)
+            }
+        }
+    }
+
+    /// Normalizes a signature to a "low S" form. In ECDSA, signatures are
+    /// of the form (r, s) where r and s are numbers lying in some finite
+    /// field. The verification equation will pass for (r, s) iff it passes
+    /// for (r, -s), so it is possible to ``modify'' signatures in transit
+    /// by flipping the sign of s. This does not constitute a forgery since
+    /// the signed message still cannot be changed, but for some applications,
+    /// changing even the signature itself can be a problem. Such applications
+    /// require a "strong signature". It is believed that ECDSA is a strong
+    /// signature except for this ambiguity in the sign of s, so to accommodate
+    /// these applications libsecp256k1 considers signatures for which s is in
+    /// the upper half of the field range invalid. This eliminates the
+    /// ambiguity.
+    ///
+    /// However, for some systems, signatures with high s-values are considered
+    /// valid. (For example, parsing the historic Bitcoin blockchain requires
+    /// this.) For these applications we provide this normalization function,
+    /// which ensures that the s value lies in the lower half of its range.
+    pub fn normalize_s(&mut self) {
+        unsafe {
+            // Ignore return value, which indicates whether the sig
+            // was already normalized. We don't care.
+            ffi::secp256k1_ecdsa_signature_normalize(
+                ffi::secp256k1_context_no_precomp,
+                self.as_mut_c_ptr(),
+                self.as_c_ptr(),
+            );
+        }
+    }
+
+    /// Obtains a raw pointer suitable for use with FFI functions
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_ptr(&self) -> *const ffi::Signature {
+        self.as_c_ptr()
+    }
+
+    /// Obtains a raw mutable pointer suitable for use with FFI functions
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_mut_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_mut_ptr(&mut self) -> *mut ffi::Signature {
+        self.as_mut_c_ptr()
+    }
+
+    #[inline]
+    /// Serializes the signature in DER format
+    pub fn serialize_der(&self) -> SerializedSignature {
+        let mut data = [0u8; serialized_signature::MAX_LEN];
+        let mut len: usize = serialized_signature::MAX_LEN;
+        unsafe {
+            let err = ffi::secp256k1_ecdsa_signature_serialize_der(
+                ffi::secp256k1_context_no_precomp,
+                data.as_mut_ptr(),
+                &mut len,
+                self.as_c_ptr(),
+            );
+            debug_assert!(err == 1);
+            SerializedSignature::from_raw_parts(data, len)
+        }
+    }
+
+    #[inline]
+    /// Serializes the signature in compact format
+    pub fn serialize_compact(&self) -> [u8; 64] {
+        let mut ret = [0u8; 64];
+        unsafe {
+            let err = ffi::secp256k1_ecdsa_signature_serialize_compact(
+                ffi::secp256k1_context_no_precomp,
+                ret.as_mut_c_ptr(),
+                self.as_c_ptr(),
+            );
+            debug_assert!(err == 1);
+        }
+        ret
+    }
+
+    /// Verifies an ECDSA signature for `msg` using `pk` and the global
+    /// [`SECP256K1`] context. The signature must be normalized or
+    /// verification will fail (see [`Signature::normalize_s`]).
+    #[inline]
+    #[cfg(feature = "global-context")]
+    pub fn verify(&self, msg: &Message, pk: &PublicKey) -> Result<(), Error> {
+        SECP256K1.verify_ecdsa(msg, self, pk)
+    }
+}
+
+impl CPtr for Signature {
+    type Target = ffi::Signature;
+
+    fn as_c_ptr(&self) -> *const Self::Target {
+        &self.0
+    }
+
+    fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
+        &mut self.0
+    }
+}
+
+/// Creates a new signature from a FFI signature
+impl From<ffi::Signature> for Signature {
+    #[inline]
+    fn from(sig: ffi::Signature) -> Signature {
+        Signature(sig)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for Signature {
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+        if s.is_human_readable() {
+            s.collect_str(self)
+        } else {
+            s.serialize_bytes(&self.serialize_der())
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for Signature {
+    fn deserialize<D: serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<Self, D::Error> {
+        if d.is_human_readable() {
+            d.deserialize_str(crate::serde_util::FromStrVisitor::new(
+                "a hex string representing a DER encoded Signature",
+            ))
+        } else {
+            d.deserialize_bytes(crate::serde_util::BytesVisitor::new(
+                "raw byte stream, that represents a DER encoded Signature",
+                Signature::from_der,
+            ))
+        }
+    }
+}
+
+impl<C: Signing> Secp256k1<C> {
+    fn sign_ecdsa_with_noncedata_pointer(
+        &self,
+        msg: &Message,
+        sk: &SecretKey,
+        noncedata: Option<&[u8; 32]>,
+    ) -> Signature {
+        unsafe {
+            let mut ret = ffi::Signature::new();
+            let noncedata_ptr = match noncedata {
+                Some(arr) => arr.as_c_ptr() as *const _,
+                None => ptr::null(),
+            };
+            // We can assume the return value because it's not possible to
+            // construct an invalid signature from a valid `Message`
+            // and `SecretKey`
+            assert_eq!(
+                ffi::secp256k1_ecdsa_sign(
+                    self.ctx.as_ptr(),
+                    &mut ret,
+                    msg.as_c_ptr(),
+                    sk.as_c_ptr(),
+                    ffi::secp256k1_nonce_function_rfc6979,
+                    noncedata_ptr
+                ),
+                1
+            );
+            Signature::from(ret)
+        }
+    }
+
+    /// Constructs a signature for `msg` using the secret key `sk` and RFC6979
+    /// nonce Requires a signing-capable context.
+    pub fn sign_ecdsa(&self, msg: &Message, sk: &SecretKey) -> Signature {
+        self.sign_ecdsa_with_noncedata_pointer(msg, sk, None)
+    }
+
+    /// Constructs a signature for `msg` using the secret key `sk` and RFC6979
+    /// nonce and includes 32 bytes of noncedata in the nonce generation via
+    /// inclusion in one of the hash operations during nonce generation.
+    /// This is useful when multiple signatures are needed for the same
+    /// Message and SecretKey while still using RFC6979. Requires a
+    /// signing-capable context.
+    pub fn sign_ecdsa_with_noncedata(
+        &self,
+        msg: &Message,
+        sk: &SecretKey,
+        noncedata: &[u8; 32],
+    ) -> Signature {
+        self.sign_ecdsa_with_noncedata_pointer(msg, sk, Some(noncedata))
+    }
+
+    fn sign_grind_with_check(
+        &self,
+        msg: &Message,
+        sk: &SecretKey,
+        check: impl Fn(&ffi::Signature) -> bool,
+    ) -> Signature {
+        let mut entropy_p: *const ffi::types::c_void = ptr::null();
+        let mut counter: u32 = 0;
+        let mut extra_entropy = [0u8; 32];
+        loop {
+            unsafe {
+                let mut ret = ffi::Signature::new();
+                // We can assume the return value because it's not possible to
+                // construct an invalid signature from a valid
+                // `Message` and `SecretKey`
+                assert_eq!(
+                    ffi::secp256k1_ecdsa_sign(
+                        self.ctx.as_ptr(),
+                        &mut ret,
+                        msg.as_c_ptr(),
+                        sk.as_c_ptr(),
+                        ffi::secp256k1_nonce_function_rfc6979,
+                        entropy_p
+                    ),
+                    1
+                );
+                if check(&ret) {
+                    return Signature::from(ret);
+                }
+
+                counter += 1;
+                extra_entropy[..4].copy_from_slice(&counter.to_le_bytes());
+                entropy_p =
+                    extra_entropy.as_c_ptr().cast::<ffi::types::c_void>();
+
+                // When fuzzing, these checks will usually spinloop forever, so
+                // just short-circuit them.
+                #[cfg(secp256k1_fuzz)]
+                return Signature::from(ret);
+            }
+        }
+    }
+
+    /// Constructs a signature for `msg` using the secret key `sk`, RFC6979
+    /// nonce and "grinds" the nonce by passing extra entropy if necessary
+    /// to produce a signature that is less than 71 - `bytes_to_grind`
+    /// bytes. The number of signing operation performed by this function is
+    /// exponential in the number of bytes grinded.
+    /// Requires a signing capable context.
+    pub fn sign_ecdsa_grind_r(
+        &self,
+        msg: &Message,
+        sk: &SecretKey,
+        bytes_to_grind: usize,
+    ) -> Signature {
+        let len_check =
+            |s: &ffi::Signature| der_length_check(s, 71 - bytes_to_grind);
+        self.sign_grind_with_check(msg, sk, len_check)
+    }
+
+    /// Constructs a signature for `msg` using the secret key `sk`, RFC6979
+    /// nonce and "grinds" the nonce by passing extra entropy if necessary
+    /// to produce a signature that is less than 71 bytes and compatible
+    /// with the low r signature implementation of bitcoin core. In average,
+    /// this function will perform two signing operations.
+    /// Requires a signing capable context.
+    pub fn sign_ecdsa_low_r(&self, msg: &Message, sk: &SecretKey) -> Signature {
+        self.sign_grind_with_check(msg, sk, compact_sig_has_zero_first_bit)
+    }
+}
+
+impl<C: Verification> Secp256k1<C> {
+    /// Checks that `sig` is a valid ECDSA signature for `msg` using the public
+    /// key `pubkey`. Returns `Ok(())` on success. Note that this function
+    /// cannot be used for Bitcoin consensus checking since there may exist
+    /// signatures which OpenSSL would verify but not libsecp256k1, or
+    /// vice-versa. Requires a verify-capable context.
+    ///
+    /// ```rust
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// # use ecash_secp256k1::{rand, Secp256k1, Message, Error};
+    /// #
+    /// # let secp = Secp256k1::new();
+    /// # let (secret_key, public_key) =
+    /// #     secp.generate_keypair(&mut rand::thread_rng());
+    /// #
+    /// let message = Message::from_digest_slice(&[0xab; 32]).expect("32bytes");
+    /// let sig = secp.sign_ecdsa(&message, &secret_key);
+    /// assert_eq!(secp.verify_ecdsa(&message, &sig, &public_key), Ok(()));
+    ///
+    /// let message = Message::from_digest_slice(&[0xcd; 32]).expect("32bytes");
+    /// assert_eq!(
+    ///     secp.verify_ecdsa(&message, &sig, &public_key),
+    ///     Err(Error::IncorrectSignature)
+    /// );
+    /// # }
+    /// ```
+    #[inline]
+    pub fn verify_ecdsa(
+        &self,
+        msg: &Message,
+        sig: &Signature,
+        pk: &PublicKey,
+    ) -> Result<(), Error> {
+        unsafe {
+            if ffi::secp256k1_ecdsa_verify(
+                self.ctx.as_ptr(),
+                sig.as_c_ptr(),
+                msg.as_c_ptr(),
+                pk.as_c_ptr(),
+            ) == 0
+            {
+                Err(Error::IncorrectSignature)
+            } else {
+                Ok(())
+            }
+        }
+    }
+}
+
+pub(crate) fn compact_sig_has_zero_first_bit(sig: &ffi::Signature) -> bool {
+    let mut compact = [0u8; 64];
+    unsafe {
+        let err = ffi::secp256k1_ecdsa_signature_serialize_compact(
+            ffi::secp256k1_context_no_precomp,
+            compact.as_mut_c_ptr(),
+            sig,
+        );
+        debug_assert!(err == 1);
+    }
+    compact[0] < 0x80
+}
+
+pub(crate) fn der_length_check(sig: &ffi::Signature, max_len: usize) -> bool {
+    let mut ser_ret = [0u8; 72];
+    let mut len: usize = ser_ret.len();
+    unsafe {
+        let err = ffi::secp256k1_ecdsa_signature_serialize_der(
+            ffi::secp256k1_context_no_precomp,
+            ser_ret.as_mut_c_ptr(),
+            &mut len,
+            sig,
+        );
+        debug_assert!(err == 1);
+    }
+    len <= max_len
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::ecdsa::Signature;
+    use crate::Scalar;
+
+    #[test]
+    fn test_from_compact_min_r_and_min_s() {
+        // From libsecp256k1: "The signature must consist of a 32-byte big
+        // endian R value, followed by a 32-byte big endian S value. If
+        // R or S fall outside of [0..order-1], the encoding is invalid.
+        // R and S with value 0 are allowed in the encoding."
+        let r = Scalar::ZERO;
+        let s = Scalar::ZERO;
+        let mut bytes: [u8; 64] = [0; 64];
+        bytes[..32].copy_from_slice(&r.to_be_bytes());
+        bytes[32..].copy_from_slice(&s.to_be_bytes());
+
+        assert!(Signature::from_compact(&bytes).is_ok())
+    }
+
+    #[test]
+    fn test_from_compact_max_r_and_max_s() {
+        let r = Scalar::MAX;
+        let s = Scalar::MAX;
+        let mut bytes: [u8; 64] = [0; 64];
+        bytes[..32].copy_from_slice(&r.to_be_bytes());
+        bytes[32..].copy_from_slice(&s.to_be_bytes());
+
+        assert!(Signature::from_compact(&bytes).is_ok())
+    }
+
+    #[test]
+    fn test_from_compact_invalid_r() {
+        let r = Scalar::MAX;
+        let s = Scalar::MAX;
+        let mut bytes: [u8; 64] = [0; 64];
+        bytes[..32].copy_from_slice(&r.to_be_bytes());
+        bytes[32..].copy_from_slice(&s.to_be_bytes());
+        bytes[31] += 1;
+
+        assert!(Signature::from_compact(&bytes).is_err())
+    }
+
+    #[test]
+    fn test_from_compact_invalid_s() {
+        let r = Scalar::MAX;
+        let s = Scalar::MAX;
+        let mut bytes: [u8; 64] = [0; 64];
+        bytes[..32].copy_from_slice(&r.to_be_bytes());
+        bytes[32..].copy_from_slice(&s.to_be_bytes());
+        bytes[63] += 1;
+
+        assert!(Signature::from_compact(&bytes).is_err())
+    }
+}
diff --git a/modules/ecash-secp256k1/src/ecdsa/recovery.rs b/modules/ecash-secp256k1/src/ecdsa/recovery.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/ecdsa/recovery.rs
@@ -0,0 +1,514 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Provides a signing function that allows recovering the public key from the
+//! signature.
+
+use core::ptr;
+
+use self::super_ffi::CPtr;
+use super::ffi as super_ffi;
+use crate::ecdsa::Signature;
+use crate::ffi::recovery as ffi;
+use crate::{key, Error, Message, Secp256k1, Signing, Verification};
+
+/// A tag used for recovering the public key from a compact signature.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum RecoveryId {
+    /// Signature recovery ID 0
+    Zero,
+    /// Signature recovery ID 1
+    One,
+    /// Signature recovery ID 2
+    Two,
+    /// Signature recovery ID 3
+    Three,
+}
+
+impl TryFrom<i32> for RecoveryId {
+    type Error = Error;
+
+    #[inline]
+    fn try_from(id: i32) -> Result<RecoveryId, Error> {
+        match id {
+            0 => Ok(RecoveryId::Zero),
+            1 => Ok(RecoveryId::One),
+            2 => Ok(RecoveryId::Two),
+            3 => Ok(RecoveryId::Three),
+            _ => Err(Error::InvalidRecoveryId),
+        }
+    }
+}
+
+impl From<RecoveryId> for i32 {
+    #[inline]
+    fn from(val: RecoveryId) -> Self {
+        match val {
+            RecoveryId::Zero => 0,
+            RecoveryId::One => 1,
+            RecoveryId::Two => 2,
+            RecoveryId::Three => 3,
+        }
+    }
+}
+
+/// An ECDSA signature with a recovery ID for pubkey recovery.
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
+pub struct RecoverableSignature(ffi::RecoverableSignature);
+
+impl RecoverableSignature {
+    #[inline]
+    /// Converts a compact-encoded byte slice to a signature. This
+    /// representation is nonstandard and defined by the libsecp256k1 library.
+    pub fn from_compact(
+        data: &[u8],
+        recid: RecoveryId,
+    ) -> Result<RecoverableSignature, Error> {
+        if data.is_empty() {
+            return Err(Error::InvalidSignature);
+        }
+
+        let mut ret = ffi::RecoverableSignature::new();
+
+        unsafe {
+            if data.len() != 64 {
+                Err(Error::InvalidSignature)
+            } else if ffi::secp256k1_ecdsa_recoverable_signature_parse_compact(
+                super_ffi::secp256k1_context_no_precomp,
+                &mut ret,
+                data.as_c_ptr(),
+                recid.into(),
+            ) == 1
+            {
+                Ok(RecoverableSignature(ret))
+            } else {
+                Err(Error::InvalidSignature)
+            }
+        }
+    }
+
+    /// Obtains a raw pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_ptr(&self) -> *const ffi::RecoverableSignature {
+        self.as_c_ptr()
+    }
+
+    /// Obtains a raw mutable pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_mut_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_mut_ptr(&mut self) -> *mut ffi::RecoverableSignature {
+        self.as_mut_c_ptr()
+    }
+
+    #[inline]
+    /// Serializes the recoverable signature in compact format.
+    pub fn serialize_compact(&self) -> (RecoveryId, [u8; 64]) {
+        let mut ret = [0u8; 64];
+        let mut recid = RecoveryId::Zero.into();
+        unsafe {
+            let err =
+                ffi::secp256k1_ecdsa_recoverable_signature_serialize_compact(
+                    super_ffi::secp256k1_context_no_precomp,
+                    ret.as_mut_c_ptr(),
+                    &mut recid,
+                    self.as_c_ptr(),
+                );
+            assert!(err == 1);
+        }
+        (
+            recid.try_into().expect("ffi returned invalid RecoveryId!"),
+            ret,
+        )
+    }
+
+    /// Converts a recoverable signature to a non-recoverable one (this is
+    /// needed for verification).
+    #[inline]
+    pub fn to_standard(&self) -> Signature {
+        unsafe {
+            let mut ret = super_ffi::Signature::new();
+            let err = ffi::secp256k1_ecdsa_recoverable_signature_convert(
+                super_ffi::secp256k1_context_no_precomp,
+                &mut ret,
+                self.as_c_ptr(),
+            );
+            assert!(err == 1);
+            Signature(ret)
+        }
+    }
+
+    /// Determines the public key for which this [`Signature`] is valid for
+    /// `msg`. Requires a verify-capable context.
+    #[inline]
+    #[cfg(feature = "global-context")]
+    pub fn recover(&self, msg: &Message) -> Result<key::PublicKey, Error> {
+        crate::SECP256K1.recover_ecdsa(msg, self)
+    }
+}
+
+impl CPtr for RecoverableSignature {
+    type Target = ffi::RecoverableSignature;
+
+    fn as_c_ptr(&self) -> *const Self::Target {
+        &self.0
+    }
+
+    fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
+        &mut self.0
+    }
+}
+
+/// Creates a new recoverable signature from a FFI one.
+impl From<ffi::RecoverableSignature> for RecoverableSignature {
+    #[inline]
+    fn from(sig: ffi::RecoverableSignature) -> RecoverableSignature {
+        RecoverableSignature(sig)
+    }
+}
+
+impl<C: Signing> Secp256k1<C> {
+    fn sign_ecdsa_recoverable_with_noncedata_pointer(
+        &self,
+        msg: &Message,
+        sk: &key::SecretKey,
+        noncedata_ptr: *const super_ffi::types::c_void,
+    ) -> RecoverableSignature {
+        let mut ret = ffi::RecoverableSignature::new();
+        unsafe {
+            // We can assume the return value because it's not possible to
+            // construct an invalid signature from a valid `Message`
+            // and `SecretKey`
+            assert_eq!(
+                ffi::secp256k1_ecdsa_sign_recoverable(
+                    self.ctx.as_ptr(),
+                    &mut ret,
+                    msg.as_c_ptr(),
+                    sk.as_c_ptr(),
+                    super_ffi::secp256k1_nonce_function_rfc6979,
+                    noncedata_ptr
+                ),
+                1
+            );
+        }
+
+        RecoverableSignature::from(ret)
+    }
+
+    /// Constructs a signature for `msg` using the secret key `sk` and RFC6979
+    /// nonce Requires a signing-capable context.
+    pub fn sign_ecdsa_recoverable(
+        &self,
+        msg: &Message,
+        sk: &key::SecretKey,
+    ) -> RecoverableSignature {
+        self.sign_ecdsa_recoverable_with_noncedata_pointer(msg, sk, ptr::null())
+    }
+
+    /// Constructs a signature for `msg` using the secret key `sk` and RFC6979
+    /// nonce and includes 32 bytes of noncedata in the nonce generation via
+    /// inclusion in one of the hash operations during nonce generation.
+    /// This is useful when multiple signatures are needed for the same
+    /// Message and SecretKey while still using RFC6979. Requires a
+    /// signing-capable context.
+    pub fn sign_ecdsa_recoverable_with_noncedata(
+        &self,
+        msg: &Message,
+        sk: &key::SecretKey,
+        noncedata: &[u8; 32],
+    ) -> RecoverableSignature {
+        let noncedata_ptr =
+            noncedata.as_ptr() as *const super_ffi::types::c_void;
+        self.sign_ecdsa_recoverable_with_noncedata_pointer(
+            msg,
+            sk,
+            noncedata_ptr,
+        )
+    }
+}
+
+impl<C: Verification> Secp256k1<C> {
+    /// Determines the public key for which `sig` is a valid signature for
+    /// `msg`. Requires a verify-capable context.
+    pub fn recover_ecdsa(
+        &self,
+        msg: &Message,
+        sig: &RecoverableSignature,
+    ) -> Result<key::PublicKey, Error> {
+        unsafe {
+            let mut pk = super_ffi::PublicKey::new();
+            if ffi::secp256k1_ecdsa_recover(
+                self.ctx.as_ptr(),
+                &mut pk,
+                sig.as_c_ptr(),
+                msg.as_c_ptr(),
+            ) != 1
+            {
+                return Err(Error::InvalidSignature);
+            }
+            Ok(key::PublicKey::from(pk))
+        }
+    }
+}
+
+#[cfg(test)]
+#[allow(unused_imports)]
+mod tests {
+    #[cfg(target_arch = "wasm32")]
+    use wasm_bindgen_test::wasm_bindgen_test as test;
+
+    use super::{RecoverableSignature, RecoveryId};
+    use crate::constants::ONE;
+    use crate::{Error, Message, Secp256k1, SecretKey};
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn capabilities() {
+        let sign = Secp256k1::signing_only();
+        let vrfy = Secp256k1::verification_only();
+        let full = Secp256k1::new();
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest_slice(&msg).unwrap();
+
+        // Try key generation
+        let (sk, pk) = full.generate_keypair(&mut rand::thread_rng());
+
+        // Try signing
+        assert_eq!(
+            sign.sign_ecdsa_recoverable(&msg, &sk),
+            full.sign_ecdsa_recoverable(&msg, &sk)
+        );
+        let sigr = full.sign_ecdsa_recoverable(&msg, &sk);
+
+        // Try pk recovery
+        assert!(vrfy.recover_ecdsa(&msg, &sigr).is_ok());
+        assert!(full.recover_ecdsa(&msg, &sigr).is_ok());
+
+        assert_eq!(
+            vrfy.recover_ecdsa(&msg, &sigr),
+            full.recover_ecdsa(&msg, &sigr)
+        );
+        assert_eq!(full.recover_ecdsa(&msg, &sigr), Ok(pk));
+    }
+
+    #[test]
+    fn recid_sanity_check() {
+        let one = RecoveryId::One;
+        assert_eq!(one, one.clone());
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))]  // fixed sig vectors can't work with fuzz-sigs
+    #[cfg(all(feature = "rand", feature = "std"))]
+    #[rustfmt::skip]
+    fn sign() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let sk = SecretKey::from_slice(&ONE).unwrap();
+        let msg = Message::from_digest_slice(&ONE).unwrap();
+
+        let sig = s.sign_ecdsa_recoverable(&msg, &sk);
+
+        assert_eq!(Ok(sig), RecoverableSignature::from_compact(&[
+            0x5c, 0xbb, 0x18, 0xc8, 0x0e, 0x78, 0x6a, 0x4d,
+            0x07, 0xdd, 0x67, 0xcb, 0x76, 0x79, 0xe7, 0x16,
+            0xde, 0xbe, 0x94, 0xf5, 0xe0, 0x7a, 0x42, 0x46,
+            0x48, 0x4b, 0x2e, 0x0d, 0x81, 0x94, 0x1c, 0xab,
+            0x4f, 0x1f, 0xbc, 0x18, 0x79, 0x14, 0x48, 0xa9,
+            0xb9, 0x87, 0xc4, 0x7c, 0x78, 0x73, 0x4c, 0xe5,
+            0x9b, 0x90, 0x07, 0x86, 0x06, 0x3b, 0x67, 0x97,
+            0x8f, 0x06, 0x86, 0x45, 0xb0, 0x86, 0x9a, 0xd8],
+            RecoveryId::One))
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))]  // fixed sig vectors can't work with fuzz-sigs
+    #[cfg(all(feature = "rand", feature = "std"))]
+    #[rustfmt::skip]
+    fn sign_with_noncedata() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let sk = SecretKey::from_slice(&ONE).unwrap();
+        let msg = Message::from_digest_slice(&ONE).unwrap();
+        let nonce = [42u8; 32];
+
+        let sig = s.sign_ecdsa_recoverable_with_noncedata(&msg, &sk, &nonce);
+
+        assert_eq!(Ok(sig), RecoverableSignature::from_compact(&[
+            0x83, 0xd7, 0x93, 0x94, 0x4c, 0x73, 0xba, 0xf1,
+            0x9c, 0x32, 0xfa, 0x40, 0x7f, 0xa5, 0x15, 0xab,
+            0x30, 0x2c, 0xd6, 0xe9, 0x76, 0xcc, 0x82, 0x0b,
+            0x9d, 0x85, 0x37, 0x70, 0x9c, 0xc1, 0x44, 0xbb,
+            0x1a, 0x30, 0x07, 0xe8, 0x8f, 0xf0, 0x10, 0x8a,
+            0x5e, 0x9c, 0xf7, 0xab, 0x0b, 0x49, 0x94, 0x8e,
+            0xc0, 0x2c, 0x7f, 0xdb, 0x47, 0x20, 0xd2, 0x21,
+            0x9d, 0xe5, 0x6a, 0x98, 0x24, 0x8f, 0x10, 0xf7],
+            RecoveryId::One))
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn sign_and_verify_fail() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest_slice(&msg).unwrap();
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+
+        let sigr = s.sign_ecdsa_recoverable(&msg, &sk);
+        let sig = sigr.to_standard();
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest_slice(&msg).unwrap();
+        assert_eq!(
+            s.verify_ecdsa(&msg, &sig, &pk),
+            Err(Error::IncorrectSignature)
+        );
+
+        let recovered_key = s.recover_ecdsa(&msg, &sigr).unwrap();
+        assert!(recovered_key != pk);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn sign_with_recovery() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest_slice(&msg).unwrap();
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+
+        let sig = s.sign_ecdsa_recoverable(&msg, &sk);
+
+        assert_eq!(s.recover_ecdsa(&msg, &sig), Ok(pk));
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn sign_with_recovery_and_noncedata() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest_slice(&msg).unwrap();
+
+        let noncedata = [42u8; 32];
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+
+        let sig =
+            s.sign_ecdsa_recoverable_with_noncedata(&msg, &sk, &noncedata);
+
+        assert_eq!(s.recover_ecdsa(&msg, &sig), Ok(pk));
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn bad_recovery() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let msg = Message::from_digest_slice(&[0x55; 32]).unwrap();
+
+        // Zero is not a valid sig
+        let sig =
+            RecoverableSignature::from_compact(&[0; 64], RecoveryId::Zero)
+                .unwrap();
+        assert_eq!(s.recover_ecdsa(&msg, &sig), Err(Error::InvalidSignature));
+        // ...but 111..111 is
+        let sig =
+            RecoverableSignature::from_compact(&[1; 64], RecoveryId::Zero)
+                .unwrap();
+        assert!(s.recover_ecdsa(&msg, &sig).is_ok());
+    }
+
+    #[test]
+    fn test_debug_output() {
+        #[rustfmt::skip]
+        let sig = RecoverableSignature::from_compact(&[
+            0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f,
+            0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6,
+            0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65,
+            0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98,
+            0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8,
+            0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f,
+            0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06,
+            0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89],
+            RecoveryId::One).unwrap();
+        assert_eq!(
+            &format!("{:?}", sig),
+            "RecoverableSignature(6673ffad2147741f04772b6f921f0ba6af0c1e77fc439\
+             e65c36dedf4092e88984c1a971652e0ada880120ef8025e709fff2080c4a39aae0\
+             68d12eed009b68c8901)",
+        );
+    }
+
+    #[test]
+    fn test_recov_sig_serialize_compact() {
+        let recid_in = RecoveryId::One;
+        #[rustfmt::skip]
+        let bytes_in = &[
+            0x66, 0x73, 0xff, 0xad, 0x21, 0x47, 0x74, 0x1f,
+            0x04, 0x77, 0x2b, 0x6f, 0x92, 0x1f, 0x0b, 0xa6,
+            0xaf, 0x0c, 0x1e, 0x77, 0xfc, 0x43, 0x9e, 0x65,
+            0xc3, 0x6d, 0xed, 0xf4, 0x09, 0x2e, 0x88, 0x98,
+            0x4c, 0x1a, 0x97, 0x16, 0x52, 0xe0, 0xad, 0xa8,
+            0x80, 0x12, 0x0e, 0xf8, 0x02, 0x5e, 0x70, 0x9f,
+            0xff, 0x20, 0x80, 0xc4, 0xa3, 0x9a, 0xae, 0x06,
+            0x8d, 0x12, 0xee, 0xd0, 0x09, 0xb6, 0x8c, 0x89];
+        let sig =
+            RecoverableSignature::from_compact(bytes_in, recid_in).unwrap();
+        let (recid_out, bytes_out) = sig.serialize_compact();
+        assert_eq!(recid_in, recid_out);
+        assert_eq!(&bytes_in[..], &bytes_out[..]);
+    }
+
+    #[test]
+    fn test_recov_id_conversion_between_i32() {
+        assert!(RecoveryId::try_from(-1i32).is_err());
+        assert!(RecoveryId::try_from(0i32).is_ok());
+        assert!(RecoveryId::try_from(1i32).is_ok());
+        assert!(RecoveryId::try_from(2i32).is_ok());
+        assert!(RecoveryId::try_from(3i32).is_ok());
+        assert!(RecoveryId::try_from(4i32).is_err());
+        let id0 = RecoveryId::Zero;
+        assert_eq!(Into::<i32>::into(id0), 0i32);
+        let id1 = RecoveryId::One;
+        assert_eq!(Into::<i32>::into(id1), 1i32);
+    }
+}
+
+#[cfg(bench)]
+// Currently only a single bench that requires "rand" + "std".
+#[cfg(all(feature = "rand", feature = "std"))]
+mod benches {
+    use test::{black_box, Bencher};
+
+    use super::{Message, Secp256k1};
+
+    #[bench]
+    pub fn bench_recover(bh: &mut Bencher) {
+        let s = Secp256k1::new();
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest_slice(&msg).unwrap();
+        let (sk, _) = s.generate_keypair(&mut rand::thread_rng());
+        let sig = s.sign_ecdsa_recoverable(&msg, &sk);
+
+        bh.iter(|| {
+            let res = s.recover_ecdsa(&msg, &sig).unwrap();
+            black_box(res);
+        });
+    }
+}
diff --git a/modules/ecash-secp256k1/src/ecdsa/serialized_signature.rs b/modules/ecash-secp256k1/src/ecdsa/serialized_signature.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/ecdsa/serialized_signature.rs
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Implements [`SerializedSignature`] and related types.
+//!
+//! DER-serialized signatures have the issue that they can have different
+//! lengths. We want to avoid using `Vec` since that would require allocations
+//! making the code slower and unable to run on platforms without allocator. We
+//! implement a special type to encapsulate serialized signatures and since it's
+//! a bit more complicated it has its own module.
+
+use core::borrow::Borrow;
+use core::{fmt, ops};
+
+pub use into_iter::IntoIter;
+
+use super::Signature;
+use crate::Error;
+
+pub(crate) const MAX_LEN: usize = 72;
+
+/// A DER serialized Signature
+#[derive(Copy, Clone)]
+pub struct SerializedSignature {
+    data: [u8; MAX_LEN],
+    len: usize,
+}
+
+impl fmt::Debug for SerializedSignature {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
+
+impl fmt::Display for SerializedSignature {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        for v in self {
+            write!(f, "{:02x}", v)?;
+        }
+        Ok(())
+    }
+}
+
+impl PartialEq for SerializedSignature {
+    #[inline]
+    fn eq(&self, other: &SerializedSignature) -> bool {
+        **self == **other
+    }
+}
+
+impl PartialEq<[u8]> for SerializedSignature {
+    #[inline]
+    fn eq(&self, other: &[u8]) -> bool {
+        **self == *other
+    }
+}
+
+impl PartialEq<SerializedSignature> for [u8] {
+    #[inline]
+    fn eq(&self, other: &SerializedSignature) -> bool {
+        *self == **other
+    }
+}
+
+impl PartialOrd for SerializedSignature {
+    fn partial_cmp(
+        &self,
+        other: &SerializedSignature,
+    ) -> Option<core::cmp::Ordering> {
+        Some((**self).cmp(&**other))
+    }
+}
+
+impl Ord for SerializedSignature {
+    fn cmp(&self, other: &SerializedSignature) -> core::cmp::Ordering {
+        (**self).cmp(&**other)
+    }
+}
+
+impl PartialOrd<[u8]> for SerializedSignature {
+    fn partial_cmp(&self, other: &[u8]) -> Option<core::cmp::Ordering> {
+        (**self).partial_cmp(other)
+    }
+}
+
+impl PartialOrd<SerializedSignature> for [u8] {
+    fn partial_cmp(
+        &self,
+        other: &SerializedSignature,
+    ) -> Option<core::cmp::Ordering> {
+        self.partial_cmp(&**other)
+    }
+}
+
+impl core::hash::Hash for SerializedSignature {
+    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
+        (**self).hash(state)
+    }
+}
+
+impl AsRef<[u8]> for SerializedSignature {
+    #[inline]
+    fn as_ref(&self) -> &[u8] {
+        self
+    }
+}
+
+impl Borrow<[u8]> for SerializedSignature {
+    #[inline]
+    fn borrow(&self) -> &[u8] {
+        self
+    }
+}
+
+impl ops::Deref for SerializedSignature {
+    type Target = [u8];
+
+    #[inline]
+    fn deref(&self) -> &[u8] {
+        &self.data[..self.len]
+    }
+}
+
+impl Eq for SerializedSignature {}
+
+impl IntoIterator for SerializedSignature {
+    type IntoIter = IntoIter;
+    type Item = u8;
+
+    #[inline]
+    fn into_iter(self) -> Self::IntoIter {
+        IntoIter::new(self)
+    }
+}
+
+impl<'a> IntoIterator for &'a SerializedSignature {
+    type IntoIter = core::slice::Iter<'a, u8>;
+    type Item = &'a u8;
+
+    #[inline]
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl From<Signature> for SerializedSignature {
+    fn from(value: Signature) -> Self {
+        Self::from_signature(&value)
+    }
+}
+
+impl<'a> From<&'a Signature> for SerializedSignature {
+    fn from(value: &'a Signature) -> Self {
+        Self::from_signature(value)
+    }
+}
+
+impl TryFrom<SerializedSignature> for Signature {
+    type Error = Error;
+
+    fn try_from(value: SerializedSignature) -> Result<Self, Self::Error> {
+        value.to_signature()
+    }
+}
+
+impl<'a> TryFrom<&'a SerializedSignature> for Signature {
+    type Error = Error;
+
+    fn try_from(value: &'a SerializedSignature) -> Result<Self, Self::Error> {
+        value.to_signature()
+    }
+}
+
+impl SerializedSignature {
+    /// Creates `SerializedSignature` from data and length.
+    ///
+    /// ## Panics
+    ///
+    /// If `len` > `MAX_LEN`
+    #[inline]
+    pub(crate) fn from_raw_parts(data: [u8; MAX_LEN], len: usize) -> Self {
+        assert!(
+            len <= MAX_LEN,
+            "attempt to set length to {} but the maximum is {}",
+            len,
+            MAX_LEN
+        );
+        SerializedSignature { data, len }
+    }
+
+    /// Get the capacity of the underlying data buffer.
+    #[deprecated = "This always returns 72"]
+    #[inline]
+    pub fn capacity(&self) -> usize {
+        self.data.len()
+    }
+
+    /// Get the len of the used data.
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.len
+    }
+
+    /// Set the length of the object.
+    #[inline]
+    pub(crate) fn set_len_unchecked(&mut self, len: usize) {
+        self.len = len;
+    }
+
+    /// Convert the serialized signature into the Signature struct.
+    /// (This DER deserializes it)
+    #[inline]
+    pub fn to_signature(&self) -> Result<Signature, Error> {
+        Signature::from_der(self)
+    }
+
+    /// Create a SerializedSignature from a Signature.
+    /// (this DER serializes it)
+    #[inline]
+    pub fn from_signature(sig: &Signature) -> SerializedSignature {
+        sig.serialize_der()
+    }
+
+    /// Check if the space is zero.
+    #[deprecated = "This always returns false"]
+    #[inline]
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+}
+
+/// Separate mod to prevent outside code accidentally breaking invariants.
+mod into_iter {
+    use super::*;
+
+    /// Owned iterator over the bytes of [`SerializedSignature`]
+    ///
+    /// Created by [`IntoIterator::into_iter`] method.
+    // allowed because of https://github.com/rust-lang/rust/issues/98348
+    #[allow(missing_copy_implementations)]
+    #[derive(Debug, Clone)]
+    pub struct IntoIter {
+        signature: SerializedSignature,
+        // invariant: pos <= signature.len()
+        pos: usize,
+    }
+
+    impl IntoIter {
+        #[inline]
+        pub(crate) fn new(signature: SerializedSignature) -> Self {
+            IntoIter {
+                signature,
+                // for all unsigned n: 0 <= n
+                pos: 0,
+            }
+        }
+
+        /// Returns the remaining bytes as a slice.
+        ///
+        /// This method is analogous to [`core::slice::Iter::as_slice`].
+        #[inline]
+        pub fn as_slice(&self) -> &[u8] {
+            &self.signature[self.pos..]
+        }
+    }
+
+    impl Iterator for IntoIter {
+        type Item = u8;
+
+        #[inline]
+        fn next(&mut self) -> Option<Self::Item> {
+            let byte = *self.signature.get(self.pos)?;
+            // can't overflow or break invariant because if pos is too large we
+            // return early
+            self.pos += 1;
+            Some(byte)
+        }
+
+        #[inline]
+        fn size_hint(&self) -> (usize, Option<usize>) {
+            // can't underlflow thanks to the invariant
+            let len = self.signature.len() - self.pos;
+            (len, Some(len))
+        }
+
+        // override for speed
+        #[inline]
+        fn nth(&mut self, n: usize) -> Option<Self::Item> {
+            if n >= self.len() {
+                // upholds invariant becasue the values will be equal
+                self.pos = self.signature.len();
+                None
+            } else {
+                // if n < signtature.len() - self.pos then n + self.pos <
+                // signature.len() which neither overflows nor
+                // breaks the invariant
+                self.pos += n;
+                self.next()
+            }
+        }
+    }
+
+    impl ExactSizeIterator for IntoIter {}
+
+    impl core::iter::FusedIterator for IntoIter {}
+
+    impl DoubleEndedIterator for IntoIter {
+        #[inline]
+        fn next_back(&mut self) -> Option<Self::Item> {
+            if self.pos == self.signature.len() {
+                return None;
+            }
+
+            // if len is 0 then pos is also 0 thanks to the invariant so we
+            // would return before we reach this
+            let new_len = self.signature.len() - 1;
+            let byte = self.signature[new_len];
+            self.signature.set_len_unchecked(new_len);
+            Some(byte)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{SerializedSignature, MAX_LEN};
+
+    #[test]
+    fn iterator_ops_are_homomorphic() {
+        let mut fake_signature_data = [0; MAX_LEN];
+        for (i, byte) in fake_signature_data.iter_mut().enumerate() {
+            *byte = i as u8; // cast ok because MAX_LEN fits in  u8.
+        }
+
+        let fake_signature = SerializedSignature {
+            data: fake_signature_data,
+            len: MAX_LEN,
+        };
+
+        let mut iter1 = fake_signature.into_iter();
+        let mut iter2 = fake_signature.iter();
+
+        // while let so we can compare size_hint and as_slice
+        while let (Some(a), Some(b)) = (iter1.next(), iter2.next()) {
+            assert_eq!(a, *b);
+            assert_eq!(iter1.size_hint(), iter2.size_hint());
+            assert_eq!(iter1.as_slice(), iter2.as_slice());
+        }
+
+        let mut iter1 = fake_signature.into_iter();
+        let mut iter2 = fake_signature.iter();
+
+        // manual next_back instead of rev() so that we can check as_slice()
+        // if next_back is implemented correctly then rev() is also correct -
+        // provided by `core`
+        while let (Some(a), Some(b)) = (iter1.next_back(), iter2.next_back()) {
+            assert_eq!(a, *b);
+            assert_eq!(iter1.size_hint(), iter2.size_hint());
+            assert_eq!(iter1.as_slice(), iter2.as_slice());
+        }
+    }
+}
diff --git a/modules/ecash-secp256k1/src/key.rs b/modules/ecash-secp256k1/src/key.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/key.rs
@@ -0,0 +1,2934 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Public and secret keys.
+
+use core::ops::{self, BitXor};
+use core::{fmt, ptr, str};
+
+#[cfg(feature = "serde")]
+use serde::ser::SerializeTuple;
+
+use crate::ffi::types::c_uint;
+use crate::ffi::{self, CPtr};
+use crate::Error::{
+    self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey,
+};
+#[cfg(feature = "hashes")]
+#[allow(deprecated)]
+use crate::ThirtyTwoByteHash;
+#[cfg(feature = "global-context")]
+use crate::SECP256K1;
+use crate::{
+    constants, ecdsa, from_hex, schnorr, Message, Scalar, Secp256k1, Signing,
+    Verification,
+};
+
+/// Secret key - a 256-bit key used to create ECDSA and Taproot signatures.
+///
+/// This value should be generated using a [cryptographically secure
+/// pseudorandom number generator].
+///
+/// # Side channel attacks
+///
+/// We have attempted to reduce the side channel attack surface by implementing
+/// a constant time `eq` method. For similar reasons we explicitly do not
+/// implement `PartialOrd`, `Ord`, or `Hash` on `SecretKey`. If you really want
+/// to order secrets keys then you can use `AsRef` to get at the underlying
+/// bytes and compare them - however this is almost certainly a bad idea.
+///
+/// # Serde support
+///
+/// Implements de/serialization with the `serde` feature enabled. We treat the
+/// byte value as a tuple of 32 `u8`s for non-human-readable formats. This
+/// representation is optimal for for some formats (e.g. [`bincode`]) however
+/// other formats may be less optimal (e.g. [`cbor`]).
+///
+/// # Examples
+///
+/// Basic usage:
+///
+/// ```
+/// # #[cfg(all(feature = "rand", feature = "std"))] {
+/// use ecash_secp256k1::{rand, Secp256k1, SecretKey};
+///
+/// let secp = Secp256k1::new();
+/// let secret_key = SecretKey::new(&mut rand::thread_rng());
+/// # }
+/// ```
+/// [`bincode`]: https://docs.rs/bincode
+/// [`cbor`]: https://docs.rs/cbor
+/// [cryptographically secure pseudorandom number generator]:
+/// https://en.wikipedia.org/wiki/\
+/// Cryptographically_secure_pseudorandom_number_generator
+#[derive(Copy, Clone)]
+pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
+impl_display_secret!(SecretKey);
+impl_non_secure_erase!(SecretKey, 0, [1u8; constants::SECRET_KEY_SIZE]);
+
+impl PartialEq for SecretKey {
+    /// This implementation is designed to be constant time to help prevent side
+    /// channel attacks.
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        let accum = self
+            .0
+            .iter()
+            .zip(&other.0)
+            .fold(0, |accum, (a, b)| accum | a ^ b);
+        unsafe { core::ptr::read_volatile(&accum) == 0 }
+    }
+}
+
+impl Eq for SecretKey {}
+
+impl AsRef<[u8; constants::SECRET_KEY_SIZE]> for SecretKey {
+    /// Gets a reference to the underlying array.
+    ///
+    /// # Side channel attacks
+    ///
+    /// Using ordering functions (`PartialOrd`/`Ord`) on a reference to secret
+    /// keys leaks data because the implementations are not constant time.
+    /// Doing so will make your code vulnerable to side channel attacks.
+    /// [`SecretKey::eq`] is implemented using a constant time algorithm,
+    /// please consider using it to do comparisons of secret keys.
+    #[inline]
+    fn as_ref(&self) -> &[u8; constants::SECRET_KEY_SIZE] {
+        let SecretKey(dat) = self;
+        dat
+    }
+}
+
+impl<I> ops::Index<I> for SecretKey
+where
+    [u8]: ops::Index<I>,
+{
+    type Output = <[u8] as ops::Index<I>>::Output;
+
+    #[inline]
+    fn index(&self, index: I) -> &Self::Output {
+        &self.0[index]
+    }
+}
+
+impl ffi::CPtr for SecretKey {
+    type Target = u8;
+
+    fn as_c_ptr(&self) -> *const Self::Target {
+        let SecretKey(dat) = self;
+        dat.as_ptr()
+    }
+
+    fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
+        let &mut SecretKey(ref mut dat) = self;
+        dat.as_mut_ptr()
+    }
+}
+
+impl str::FromStr for SecretKey {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<SecretKey, Error> {
+        let mut res = [0u8; constants::SECRET_KEY_SIZE];
+        match from_hex(s, &mut res) {
+            Ok(constants::SECRET_KEY_SIZE) => SecretKey::from_byte_array(&res),
+            _ => Err(Error::InvalidSecretKey),
+        }
+    }
+}
+
+/// Public key - used to verify ECDSA signatures and to do Taproot tweaks.
+///
+/// # Serde support
+///
+/// Implements de/serialization with the `serde` feature enabled. We treat the
+/// byte value as a tuple of 33 `u8`s for non-human-readable formats. This
+/// representation is optimal for for some formats (e.g. [`bincode`]) however
+/// other formats may be less optimal (e.g. [`cbor`]).
+///
+/// # Examples
+///
+/// Basic usage:
+///
+/// ```
+/// # #[cfg(feature =  "alloc")] {
+/// use ecash_secp256k1::{PublicKey, Secp256k1, SecretKey};
+///
+/// let secp = Secp256k1::new();
+/// let secret_key = SecretKey::from_byte_array(&[0xcd; 32])
+///     .expect("32 bytes, within curve order");
+/// let public_key = PublicKey::from_secret_key(&secp, &secret_key);
+/// # }
+/// ```
+/// [`bincode`]: https://docs.rs/bincode
+/// [`cbor`]: https://docs.rs/cbor
+#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+#[repr(transparent)]
+pub struct PublicKey(ffi::PublicKey);
+impl_fast_comparisons!(PublicKey);
+
+impl fmt::LowerHex for PublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let ser = self.serialize();
+        for ch in &ser[..] {
+            write!(f, "{:02x}", *ch)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for PublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
+impl fmt::Debug for PublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
+impl str::FromStr for PublicKey {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<PublicKey, Error> {
+        let mut res = [0u8; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE];
+        match from_hex(s, &mut res) {
+            Ok(constants::PUBLIC_KEY_SIZE) => {
+                let bytes: [u8; constants::PUBLIC_KEY_SIZE] =
+                    res[0..constants::PUBLIC_KEY_SIZE].try_into().unwrap();
+                PublicKey::from_byte_array_compressed(&bytes)
+            }
+            Ok(constants::UNCOMPRESSED_PUBLIC_KEY_SIZE) => {
+                PublicKey::from_byte_array_uncompressed(&res)
+            }
+            _ => Err(Error::InvalidPublicKey),
+        }
+    }
+}
+
+impl SecretKey {
+    /// Generates a new random secret key.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "std", feature =  "rand"))] {
+    /// use ecash_secp256k1::{rand, SecretKey};
+    /// let secret_key = SecretKey::new(&mut rand::thread_rng());
+    /// # }
+    /// ```
+    #[inline]
+    #[cfg(feature = "rand")]
+    pub fn new<R: rand::Rng + ?Sized>(rng: &mut R) -> SecretKey {
+        let mut data = crate::random_32_bytes(rng);
+        unsafe {
+            while ffi::secp256k1_ec_seckey_verify(
+                ffi::secp256k1_context_no_precomp,
+                data.as_c_ptr(),
+            ) == 0
+            {
+                data = crate::random_32_bytes(rng);
+            }
+        }
+        SecretKey(data)
+    }
+
+    /// Converts a 32-byte slice to a secret key.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ecash_secp256k1::SecretKey;
+    /// let sk = SecretKey::from_slice(&[0xcd; 32])
+    ///     .expect("32 bytes, within curve order");
+    /// ```
+    #[deprecated(since = "TBD", note = "Use `from_byte_array` instead.")]
+    #[inline]
+    pub fn from_slice(data: &[u8]) -> Result<SecretKey, Error> {
+        match <[u8; constants::SECRET_KEY_SIZE]>::try_from(data) {
+            Ok(data) => Self::from_byte_array(&data),
+            Err(_) => Err(InvalidSecretKey),
+        }
+    }
+
+    /// Converts a 32-byte array to a secret key.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ecash_secp256k1::SecretKey;
+    /// let sk = SecretKey::from_byte_array(&[0xcd; 32])
+    ///     .expect("32 bytes, within curve order");
+    /// ```
+    #[inline]
+    pub fn from_byte_array(
+        data: &[u8; constants::SECRET_KEY_SIZE],
+    ) -> Result<SecretKey, Error> {
+        unsafe {
+            if ffi::secp256k1_ec_seckey_verify(
+                ffi::secp256k1_context_no_precomp,
+                data.as_c_ptr(),
+            ) == 0
+            {
+                return Err(InvalidSecretKey);
+            }
+        }
+        Ok(SecretKey(*data))
+    }
+
+    /// Creates a new secret key using data from BIP-340 [`Keypair`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{rand, Keypair, Secp256k1, SecretKey};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let keypair = Keypair::new(&secp, &mut rand::thread_rng());
+    /// let secret_key = SecretKey::from_keypair(&keypair);
+    /// # }
+    /// ```
+    #[inline]
+    pub fn from_keypair(keypair: &Keypair) -> Self {
+        let mut sk = [0u8; constants::SECRET_KEY_SIZE];
+        unsafe {
+            let ret = ffi::secp256k1_keypair_sec(
+                ffi::secp256k1_context_no_precomp,
+                sk.as_mut_c_ptr(),
+                keypair.as_c_ptr(),
+            );
+            debug_assert_eq!(ret, 1);
+        }
+        SecretKey(sk)
+    }
+
+    /// Returns the secret key as a byte value.
+    #[inline]
+    pub fn secret_bytes(&self) -> [u8; constants::SECRET_KEY_SIZE] {
+        self.0
+    }
+
+    /// Negates the secret key.
+    #[inline]
+    #[must_use = "you forgot to use the negated secret key"]
+    pub fn negate(mut self) -> SecretKey {
+        unsafe {
+            let res = ffi::secp256k1_ec_seckey_negate(
+                ffi::secp256k1_context_no_precomp,
+                self.as_mut_c_ptr(),
+            );
+            debug_assert_eq!(res, 1);
+        }
+        self
+    }
+
+    /// Tweaks a [`SecretKey`] by adding `tweak` modulo the curve order.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the resulting key would be invalid.
+    #[inline]
+    pub fn add_tweak(mut self, tweak: &Scalar) -> Result<SecretKey, Error> {
+        unsafe {
+            if ffi::secp256k1_ec_seckey_tweak_add(
+                ffi::secp256k1_context_no_precomp,
+                self.as_mut_c_ptr(),
+                tweak.as_c_ptr(),
+            ) != 1
+            {
+                Err(Error::InvalidTweak)
+            } else {
+                Ok(self)
+            }
+        }
+    }
+
+    /// Tweaks a [`SecretKey`] by multiplying by `tweak` modulo the curve order.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the resulting key would be invalid.
+    #[inline]
+    pub fn mul_tweak(mut self, tweak: &Scalar) -> Result<SecretKey, Error> {
+        unsafe {
+            if ffi::secp256k1_ec_seckey_tweak_mul(
+                ffi::secp256k1_context_no_precomp,
+                self.as_mut_c_ptr(),
+                tweak.as_c_ptr(),
+            ) != 1
+            {
+                Err(Error::InvalidTweak)
+            } else {
+                Ok(self)
+            }
+        }
+    }
+
+    /// Constructs an ECDSA signature for `msg` using the global [`SECP256K1`]
+    /// context.
+    #[inline]
+    #[cfg(feature = "global-context")]
+    pub fn sign_ecdsa(&self, msg: Message) -> ecdsa::Signature {
+        SECP256K1.sign_ecdsa(&msg, self)
+    }
+
+    /// Returns the [`Keypair`] for this [`SecretKey`].
+    ///
+    /// This is equivalent to using [`Keypair::from_secret_key`].
+    #[inline]
+    pub fn keypair<C: Signing>(&self, secp: &Secp256k1<C>) -> Keypair {
+        Keypair::from_secret_key(secp, self)
+    }
+
+    /// Returns the [`PublicKey`] for this [`SecretKey`].
+    ///
+    /// This is equivalent to using [`PublicKey::from_secret_key`].
+    #[inline]
+    pub fn public_key<C: Signing>(&self, secp: &Secp256k1<C>) -> PublicKey {
+        PublicKey::from_secret_key(secp, self)
+    }
+
+    /// Returns the [`XOnlyPublicKey`] (and it's [`Parity`]) for this
+    /// [`SecretKey`].
+    ///
+    /// This is equivalent to
+    /// `XOnlyPublicKey::from_keypair(self.keypair(secp))`.
+    #[inline]
+    pub fn x_only_public_key<C: Signing>(
+        &self,
+        secp: &Secp256k1<C>,
+    ) -> (XOnlyPublicKey, Parity) {
+        let kp = self.keypair(secp);
+        XOnlyPublicKey::from_keypair(&kp)
+    }
+}
+
+#[cfg(feature = "hashes")]
+#[allow(deprecated)]
+impl<T: ThirtyTwoByteHash> From<T> for SecretKey {
+    /// Converts a 32-byte hash directly to a secret key without error paths.
+    fn from(t: T) -> SecretKey {
+        SecretKey::from_byte_array(&t.into_32())
+            .expect("failed to create secret key")
+    }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for SecretKey {
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+        if s.is_human_readable() {
+            let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2];
+            s.serialize_str(
+                crate::to_hex(&self.0, &mut buf)
+                    .expect("fixed-size hex serialization"),
+            )
+        } else {
+            let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
+            for byte in self.0.iter() {
+                tuple.serialize_element(byte)?;
+            }
+            tuple.end()
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for SecretKey {
+    fn deserialize<D: serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<Self, D::Error> {
+        if d.is_human_readable() {
+            d.deserialize_str(super::serde_util::FromStrVisitor::new(
+                "a hex string representing 32 byte SecretKey",
+            ))
+        } else {
+            let visitor = super::serde_util::Tuple32Visitor::new(
+                "raw 32 bytes SecretKey",
+                SecretKey::from_slice,
+            );
+            d.deserialize_tuple(constants::SECRET_KEY_SIZE, visitor)
+        }
+    }
+}
+
+impl PublicKey {
+    /// Obtains a raw const pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_ptr(&self) -> *const ffi::PublicKey {
+        self.as_c_ptr()
+    }
+
+    /// Obtains a raw mutable pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_mut_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_mut_ptr(&mut self) -> *mut ffi::PublicKey {
+        self.as_mut_c_ptr()
+    }
+
+    /// Creates a new public key from a [`SecretKey`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{rand, PublicKey, Secp256k1, SecretKey};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let secret_key = SecretKey::new(&mut rand::thread_rng());
+    /// let public_key = PublicKey::from_secret_key(&secp, &secret_key);
+    /// # }
+    /// ```
+    #[inline]
+    pub fn from_secret_key<C: Signing>(
+        secp: &Secp256k1<C>,
+        sk: &SecretKey,
+    ) -> PublicKey {
+        unsafe {
+            let mut pk = ffi::PublicKey::new();
+            // We can assume the return value because it's not possible to
+            // construct an invalid `SecretKey` without transmute
+            // trickery or something.
+            let res = ffi::secp256k1_ec_pubkey_create(
+                secp.ctx.as_ptr(),
+                &mut pk,
+                sk.as_c_ptr(),
+            );
+            debug_assert_eq!(res, 1);
+            PublicKey(pk)
+        }
+    }
+
+    /// Creates a new public key from a [`SecretKey`] and the global
+    /// [`SECP256K1`] context.
+    #[inline]
+    #[cfg(feature = "global-context")]
+    pub fn from_secret_key_global(sk: &SecretKey) -> PublicKey {
+        PublicKey::from_secret_key(SECP256K1, sk)
+    }
+
+    /// Creates a public key directly from a slice.
+    #[inline]
+    pub fn from_slice(data: &[u8]) -> Result<PublicKey, Error> {
+        match data.len() {
+            constants::PUBLIC_KEY_SIZE => {
+                PublicKey::from_byte_array_compressed(
+                    &<[u8; constants::PUBLIC_KEY_SIZE]>::try_from(data)
+                        .unwrap(),
+                )
+            }
+            constants::UNCOMPRESSED_PUBLIC_KEY_SIZE => {
+                PublicKey::from_byte_array_uncompressed(
+                    &<[u8; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE]>::try_from(
+                        data,
+                    )
+                    .unwrap(),
+                )
+            }
+            _ => Err(InvalidPublicKey),
+        }
+    }
+
+    /// Creates a public key from a serialized array in compressed format.
+    #[inline]
+    pub fn from_byte_array_compressed(
+        data: &[u8; constants::PUBLIC_KEY_SIZE],
+    ) -> Result<PublicKey, Error> {
+        unsafe {
+            let mut pk = ffi::PublicKey::new();
+            if ffi::secp256k1_ec_pubkey_parse(
+                ffi::secp256k1_context_no_precomp,
+                &mut pk,
+                data.as_c_ptr(),
+                constants::PUBLIC_KEY_SIZE,
+            ) == 1
+            {
+                Ok(PublicKey(pk))
+            } else {
+                Err(InvalidPublicKey)
+            }
+        }
+    }
+
+    /// Creates a public key from a serialized array in uncompressed format.
+    #[inline]
+    pub fn from_byte_array_uncompressed(
+        data: &[u8; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE],
+    ) -> Result<PublicKey, Error> {
+        unsafe {
+            let mut pk = ffi::PublicKey::new();
+            if ffi::secp256k1_ec_pubkey_parse(
+                ffi::secp256k1_context_no_precomp,
+                &mut pk,
+                data.as_c_ptr(),
+                constants::UNCOMPRESSED_PUBLIC_KEY_SIZE,
+            ) == 1
+            {
+                Ok(PublicKey(pk))
+            } else {
+                Err(InvalidPublicKey)
+            }
+        }
+    }
+
+    /// Creates a new compressed public key using data from BIP-340 [`Keypair`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{rand, Keypair, PublicKey, Secp256k1};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let keypair = Keypair::new(&secp, &mut rand::thread_rng());
+    /// let public_key = PublicKey::from_keypair(&keypair);
+    /// # }
+    /// ```
+    #[inline]
+    pub fn from_keypair(keypair: &Keypair) -> Self {
+        unsafe {
+            let mut pk = ffi::PublicKey::new();
+            let ret = ffi::secp256k1_keypair_pub(
+                ffi::secp256k1_context_no_precomp,
+                &mut pk,
+                keypair.as_c_ptr(),
+            );
+            debug_assert_eq!(ret, 1);
+            PublicKey(pk)
+        }
+    }
+
+    /// Creates a [`PublicKey`] using the key material from `pk` combined with
+    /// the `parity`.
+    pub fn from_x_only_public_key(
+        pk: XOnlyPublicKey,
+        parity: Parity,
+    ) -> PublicKey {
+        let mut buf = [0u8; 33];
+
+        // First byte of a compressed key should be `0x02 AND parity`.
+        buf[0] = match parity {
+            Parity::Even => 0x02,
+            Parity::Odd => 0x03,
+        };
+        buf[1..].clone_from_slice(&pk.serialize());
+
+        PublicKey::from_byte_array_compressed(&buf)
+            .expect("we know the buffer is valid")
+    }
+
+    #[inline]
+    /// Serializes the key as a byte-encoded pair of values. In compressed form
+    /// the y-coordinate is represented by only a single bit, as x
+    /// determines it up to one bit.
+    pub fn serialize(&self) -> [u8; constants::PUBLIC_KEY_SIZE] {
+        let mut ret = [0u8; constants::PUBLIC_KEY_SIZE];
+        self.serialize_internal(&mut ret, ffi::SECP256K1_SER_COMPRESSED);
+        ret
+    }
+
+    #[inline]
+    /// Serializes the key as a byte-encoded pair of values, in uncompressed
+    /// form.
+    pub fn serialize_uncompressed(
+        &self,
+    ) -> [u8; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE] {
+        let mut ret = [0u8; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE];
+        self.serialize_internal(&mut ret, ffi::SECP256K1_SER_UNCOMPRESSED);
+        ret
+    }
+
+    #[inline(always)]
+    fn serialize_internal(&self, ret: &mut [u8], flag: c_uint) {
+        let mut ret_len = ret.len();
+        let res = unsafe {
+            ffi::secp256k1_ec_pubkey_serialize(
+                ffi::secp256k1_context_no_precomp,
+                ret.as_mut_c_ptr(),
+                &mut ret_len,
+                self.as_c_ptr(),
+                flag,
+            )
+        };
+        debug_assert_eq!(res, 1);
+        debug_assert_eq!(ret_len, ret.len());
+    }
+
+    /// Negates the public key.
+    #[inline]
+    #[must_use = "you forgot to use the negated public key"]
+    pub fn negate<C: Verification>(mut self, secp: &Secp256k1<C>) -> PublicKey {
+        unsafe {
+            let res =
+                ffi::secp256k1_ec_pubkey_negate(secp.ctx.as_ptr(), &mut self.0);
+            debug_assert_eq!(res, 1);
+        }
+        self
+    }
+
+    /// Tweaks a [`PublicKey`] by adding `tweak * G` modulo the curve order.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the resulting key would be invalid.
+    #[inline]
+    pub fn add_exp_tweak<C: Verification>(
+        mut self,
+        secp: &Secp256k1<C>,
+        tweak: &Scalar,
+    ) -> Result<PublicKey, Error> {
+        unsafe {
+            if ffi::secp256k1_ec_pubkey_tweak_add(
+                secp.ctx.as_ptr(),
+                &mut self.0,
+                tweak.as_c_ptr(),
+            ) == 1
+            {
+                Ok(self)
+            } else {
+                Err(Error::InvalidTweak)
+            }
+        }
+    }
+
+    /// Tweaks a [`PublicKey`] by multiplying by `tweak` modulo the curve order.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the resulting key would be invalid.
+    #[inline]
+    pub fn mul_tweak<C: Verification>(
+        mut self,
+        secp: &Secp256k1<C>,
+        other: &Scalar,
+    ) -> Result<PublicKey, Error> {
+        unsafe {
+            if ffi::secp256k1_ec_pubkey_tweak_mul(
+                secp.ctx.as_ptr(),
+                &mut self.0,
+                other.as_c_ptr(),
+            ) == 1
+            {
+                Ok(self)
+            } else {
+                Err(Error::InvalidTweak)
+            }
+        }
+    }
+
+    /// Adds a second key to this one, returning the sum.
+    ///
+    /// # Errors
+    ///
+    /// If the result would be the point at infinity, i.e. adding this point to
+    /// its own negation.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{rand, Secp256k1};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let mut rng = rand::thread_rng();
+    /// let (_, pk1) = secp.generate_keypair(&mut rng);
+    /// let (_, pk2) = secp.generate_keypair(&mut rng);
+    /// let sum = pk1
+    ///     .combine(&pk2)
+    ///     .expect("It's improbable to fail for 2 random public keys");
+    /// # }
+    /// ```
+    pub fn combine(&self, other: &PublicKey) -> Result<PublicKey, Error> {
+        PublicKey::combine_keys(&[self, other])
+    }
+
+    /// Adds the keys in the provided slice together, returning the sum.
+    ///
+    /// # Errors
+    ///
+    /// Errors under any of the following conditions:
+    /// - The result would be the point at infinity, i.e. adding a point to its
+    ///   own negation.
+    /// - The provided slice is empty.
+    /// - The number of elements in the provided slice is greater than
+    ///   `i32::MAX`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{rand, PublicKey, Secp256k1};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let mut rng = rand::thread_rng();
+    /// let (_, pk1) = secp.generate_keypair(&mut rng);
+    /// let (_, pk2) = secp.generate_keypair(&mut rng);
+    /// let (_, pk3) = secp.generate_keypair(&mut rng);
+    /// let sum = PublicKey::combine_keys(&[&pk1, &pk2, &pk3])
+    ///     .expect("It's improbable to fail for 3 random public keys");
+    /// # }
+    /// ```
+    pub fn combine_keys(keys: &[&PublicKey]) -> Result<PublicKey, Error> {
+        use core::mem::transmute;
+
+        if keys.is_empty() || keys.len() > i32::MAX as usize {
+            return Err(InvalidPublicKeySum);
+        }
+
+        unsafe {
+            let mut ret = ffi::PublicKey::new();
+            let ptrs: &[*const ffi::PublicKey] =
+                transmute::<&[&PublicKey], &[*const ffi::PublicKey]>(keys);
+            if ffi::secp256k1_ec_pubkey_combine(
+                ffi::secp256k1_context_no_precomp,
+                &mut ret,
+                ptrs.as_c_ptr(),
+                keys.len(),
+            ) == 1
+            {
+                Ok(PublicKey(ret))
+            } else {
+                Err(InvalidPublicKeySum)
+            }
+        }
+    }
+
+    /// Returns the [`XOnlyPublicKey`] (and it's [`Parity`]) for this
+    /// [`PublicKey`].
+    #[inline]
+    pub fn x_only_public_key(&self) -> (XOnlyPublicKey, Parity) {
+        let mut pk_parity = 0;
+        unsafe {
+            let mut xonly_pk = ffi::XOnlyPublicKey::new();
+            let ret = ffi::secp256k1_xonly_pubkey_from_pubkey(
+                ffi::secp256k1_context_no_precomp,
+                &mut xonly_pk,
+                &mut pk_parity,
+                self.as_c_ptr(),
+            );
+            debug_assert_eq!(ret, 1);
+            let parity = Parity::from_i32(pk_parity)
+                .expect("should not panic, pk_parity is 0 or 1");
+
+            (XOnlyPublicKey(xonly_pk), parity)
+        }
+    }
+
+    /// Checks that `sig` is a valid ECDSA signature for `msg` using this public
+    /// key.
+    pub fn verify<C: Verification>(
+        &self,
+        secp: &Secp256k1<C>,
+        msg: &Message,
+        sig: &ecdsa::Signature,
+    ) -> Result<(), Error> {
+        secp.verify_ecdsa(msg, sig, self)
+    }
+}
+
+/// This trait enables interaction with the FFI layer and even though it is part
+/// of the public API normal users should never need to directly interact with
+/// FFI types.
+impl CPtr for PublicKey {
+    type Target = ffi::PublicKey;
+
+    /// Obtains a const pointer suitable for use with FFI functions.
+    fn as_c_ptr(&self) -> *const Self::Target {
+        &self.0
+    }
+
+    /// Obtains a mutable pointer suitable for use with FFI functions.
+    fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
+        &mut self.0
+    }
+}
+
+/// Creates a new public key from a FFI public key.
+///
+/// Note, normal users should never need to interact directly with FFI types.
+impl From<ffi::PublicKey> for PublicKey {
+    #[inline]
+    fn from(pk: ffi::PublicKey) -> PublicKey {
+        PublicKey(pk)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for PublicKey {
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+        if s.is_human_readable() {
+            s.collect_str(self)
+        } else {
+            let mut tuple = s.serialize_tuple(constants::PUBLIC_KEY_SIZE)?;
+            // Serialize in compressed form.
+            for byte in self.serialize().iter() {
+                tuple.serialize_element(&byte)?;
+            }
+            tuple.end()
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for PublicKey {
+    fn deserialize<D: serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<PublicKey, D::Error> {
+        if d.is_human_readable() {
+            d.deserialize_str(super::serde_util::FromStrVisitor::new(
+                "an ASCII hex string representing a public key",
+            ))
+        } else {
+            let visitor = super::serde_util::Tuple33Visitor::new(
+                "33 bytes compressed public key",
+                PublicKey::from_slice,
+            );
+            d.deserialize_tuple(constants::PUBLIC_KEY_SIZE, visitor)
+        }
+    }
+}
+
+/// Opaque data structure that holds a keypair consisting of a secret and a
+/// public key.
+///
+/// # Serde support
+///
+/// Implements de/serialization with the `serde` and_`global-context` features
+/// enabled. Serializes the secret bytes only. We treat the byte value as a
+/// tuple of 32 `u8`s for non-human-readable formats. This representation is
+/// optimal for for some formats (e.g. [`bincode`]) however other formats may be
+/// less optimal (e.g. [`cbor`]). For human-readable formats we use a hex
+/// string.
+///
+/// # Examples
+///
+/// Basic usage:
+///
+/// ```
+/// # #[cfg(all(feature = "rand", feature = "std"))] {
+/// use ecash_secp256k1::{rand, Keypair, Secp256k1};
+///
+/// let secp = Secp256k1::new();
+/// let (secret_key, public_key) =
+///     secp.generate_keypair(&mut rand::thread_rng());
+/// let keypair = Keypair::from_secret_key(&secp, &secret_key);
+/// # }
+/// ```
+/// [`bincode`]: https://docs.rs/bincode
+/// [`cbor`]: https://docs.rs/cbor
+#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct Keypair(ffi::Keypair);
+impl_fast_comparisons!(Keypair);
+
+impl Keypair {
+    /// Obtains a raw const pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_ptr(&self) -> *const ffi::Keypair {
+        self.as_c_ptr()
+    }
+
+    /// Obtains a raw mutable pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_mut_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_mut_ptr(&mut self) -> *mut ffi::Keypair {
+        self.as_mut_c_ptr()
+    }
+
+    /// Creates a [`Keypair`] directly from a Secp256k1 secret key.
+    #[inline]
+    pub fn from_secret_key<C: Signing>(
+        secp: &Secp256k1<C>,
+        sk: &SecretKey,
+    ) -> Keypair {
+        unsafe {
+            let mut kp = ffi::Keypair::new();
+            if ffi::secp256k1_keypair_create(
+                secp.ctx.as_ptr(),
+                &mut kp,
+                sk.as_c_ptr(),
+            ) == 1
+            {
+                Keypair(kp)
+            } else {
+                panic!(
+                    "the provided secret key is invalid: it is corrupted or \
+                     was not produced by Secp256k1 library"
+                )
+            }
+        }
+    }
+
+    /// Creates a [`Keypair`] directly from a secret key slice.
+    ///
+    /// # Errors
+    ///
+    /// [`Error::InvalidSecretKey`] if the provided data has an incorrect
+    /// length, exceeds Secp256k1 field `p` value or the corresponding
+    /// public key is not even.
+    #[inline]
+    pub fn from_seckey_slice<C: Signing>(
+        secp: &Secp256k1<C>,
+        data: &[u8],
+    ) -> Result<Keypair, Error> {
+        if data.is_empty() || data.len() != constants::SECRET_KEY_SIZE {
+            return Err(Error::InvalidSecretKey);
+        }
+
+        unsafe {
+            let mut kp = ffi::Keypair::new();
+            if ffi::secp256k1_keypair_create(
+                secp.ctx.as_ptr(),
+                &mut kp,
+                data.as_c_ptr(),
+            ) == 1
+            {
+                Ok(Keypair(kp))
+            } else {
+                Err(Error::InvalidSecretKey)
+            }
+        }
+    }
+
+    /// Creates a [`Keypair`] directly from a secret key string.
+    ///
+    /// # Errors
+    ///
+    /// [`Error::InvalidSecretKey`] if corresponding public key for the provided
+    /// secret key is not even.
+    #[inline]
+    pub fn from_seckey_str<C: Signing>(
+        secp: &Secp256k1<C>,
+        s: &str,
+    ) -> Result<Keypair, Error> {
+        let mut res = [0u8; constants::SECRET_KEY_SIZE];
+        match from_hex(s, &mut res) {
+            Ok(constants::SECRET_KEY_SIZE) => Keypair::from_seckey_slice(
+                secp,
+                &res[0..constants::SECRET_KEY_SIZE],
+            ),
+            _ => Err(Error::InvalidPublicKey),
+        }
+    }
+
+    /// Creates a [`Keypair`] directly from a secret key string and the global
+    /// [`SECP256K1`] context.
+    ///
+    /// # Errors
+    ///
+    /// [`Error::InvalidSecretKey`] if corresponding public key for the provided
+    /// secret key is not even.
+    #[inline]
+    #[cfg(feature = "global-context")]
+    pub fn from_seckey_str_global(s: &str) -> Result<Keypair, Error> {
+        Keypair::from_seckey_str(SECP256K1, s)
+    }
+
+    /// Generates a new random secret key.
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{rand, Keypair, Secp256k1, SecretKey};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let keypair = Keypair::new(&secp, &mut rand::thread_rng());
+    /// # }
+    /// ```
+    #[inline]
+    #[cfg(feature = "rand")]
+    pub fn new<R: rand::Rng + ?Sized, C: Signing>(
+        secp: &Secp256k1<C>,
+        rng: &mut R,
+    ) -> Keypair {
+        let mut data = crate::random_32_bytes(rng);
+        unsafe {
+            let mut keypair = ffi::Keypair::new();
+            while ffi::secp256k1_keypair_create(
+                secp.ctx.as_ptr(),
+                &mut keypair,
+                data.as_c_ptr(),
+            ) == 0
+            {
+                data = crate::random_32_bytes(rng);
+            }
+            Keypair(keypair)
+        }
+    }
+
+    /// Generates a new random secret key using the global [`SECP256K1`]
+    /// context.
+    #[inline]
+    #[cfg(all(feature = "global-context", feature = "rand"))]
+    pub fn new_global<R: ::rand::Rng + ?Sized>(rng: &mut R) -> Keypair {
+        Keypair::new(SECP256K1, rng)
+    }
+
+    /// Returns the secret bytes for this key pair.
+    #[inline]
+    pub fn secret_bytes(&self) -> [u8; constants::SECRET_KEY_SIZE] {
+        *SecretKey::from_keypair(self).as_ref()
+    }
+
+    /// Tweaks a keypair by first converting the public key to an xonly key and
+    /// tweaking it.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the resulting key would be invalid.
+    ///
+    /// NB: Will not error if the tweaked public key has an odd value and can't
+    /// be used for     BIP 340-342 purposes.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{Keypair, Scalar, Secp256k1};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let tweak = Scalar::random();
+    ///
+    /// let mut keypair = Keypair::new(&secp, &mut rand::thread_rng());
+    /// let tweaked = keypair
+    ///     .add_xonly_tweak(&secp, &tweak)
+    ///     .expect("Improbable to fail with a randomly generated tweak");
+    /// # }
+    /// ```
+    // TODO: Add checked implementation
+    #[inline]
+    pub fn add_xonly_tweak<C: Verification>(
+        mut self,
+        secp: &Secp256k1<C>,
+        tweak: &Scalar,
+    ) -> Result<Keypair, Error> {
+        unsafe {
+            let err = ffi::secp256k1_keypair_xonly_tweak_add(
+                secp.ctx.as_ptr(),
+                &mut self.0,
+                tweak.as_c_ptr(),
+            );
+            if err != 1 {
+                return Err(Error::InvalidTweak);
+            }
+
+            Ok(self)
+        }
+    }
+
+    /// Returns the [`SecretKey`] for this [`Keypair`].
+    ///
+    /// This is equivalent to using [`SecretKey::from_keypair`].
+    #[inline]
+    pub fn secret_key(&self) -> SecretKey {
+        SecretKey::from_keypair(self)
+    }
+
+    /// Returns the [`PublicKey`] for this [`Keypair`].
+    ///
+    /// This is equivalent to using [`PublicKey::from_keypair`].
+    #[inline]
+    pub fn public_key(&self) -> PublicKey {
+        PublicKey::from_keypair(self)
+    }
+
+    /// Returns the [`XOnlyPublicKey`] (and it's [`Parity`]) for this
+    /// [`Keypair`].
+    ///
+    /// This is equivalent to using [`XOnlyPublicKey::from_keypair`].
+    #[inline]
+    pub fn x_only_public_key(&self) -> (XOnlyPublicKey, Parity) {
+        XOnlyPublicKey::from_keypair(self)
+    }
+
+    /// Constructs an schnorr signature for `msg` using the global [`SECP256K1`]
+    /// context.
+    #[inline]
+    #[cfg(all(feature = "global-context", feature = "rand", feature = "std"))]
+    pub fn sign_schnorr(&self, msg: &[u8]) -> schnorr::Signature {
+        SECP256K1.sign_schnorr(msg, self)
+    }
+
+    /// Attempts to erase the secret within the underlying array.
+    ///
+    /// Note, however, that the compiler is allowed to freely copy or move the
+    /// contents of this array to other places in memory. Preventing this
+    /// behavior is very subtle. For more discussion on this, please see the
+    /// documentation of the [`zeroize`](https://docs.rs/zeroize) crate.
+    #[inline]
+    pub fn non_secure_erase(&mut self) {
+        self.0.non_secure_erase();
+    }
+}
+
+impl fmt::Debug for Keypair {
+    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+        f.debug_struct("Keypair")
+            .field("pubkey", &self.public_key())
+            .field("secret", &"<hidden>")
+            .finish()
+    }
+}
+
+impl From<Keypair> for SecretKey {
+    #[inline]
+    fn from(pair: Keypair) -> Self {
+        SecretKey::from_keypair(&pair)
+    }
+}
+
+impl<'a> From<&'a Keypair> for SecretKey {
+    #[inline]
+    fn from(pair: &'a Keypair) -> Self {
+        SecretKey::from_keypair(pair)
+    }
+}
+
+impl From<Keypair> for PublicKey {
+    #[inline]
+    fn from(pair: Keypair) -> Self {
+        PublicKey::from_keypair(&pair)
+    }
+}
+
+impl<'a> From<&'a Keypair> for PublicKey {
+    #[inline]
+    fn from(pair: &'a Keypair) -> Self {
+        PublicKey::from_keypair(pair)
+    }
+}
+
+#[cfg(any(feature = "global-context", feature = "alloc"))]
+impl str::FromStr for Keypair {
+    type Err = Error;
+
+    // When built with no default features.
+    #[allow(unused_variables, unreachable_code)]
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        #[cfg(feature = "global-context")]
+        let ctx = SECP256K1;
+
+        #[cfg(all(not(feature = "global-context"), feature = "alloc"))]
+        let ctx = Secp256k1::signing_only();
+
+        #[allow(clippy::needless_borrow)]
+        Keypair::from_seckey_str(&ctx, s)
+    }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for Keypair {
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+        if s.is_human_readable() {
+            let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2];
+            s.serialize_str(
+                crate::to_hex(&self.secret_bytes(), &mut buf)
+                    .expect("fixed-size hex serialization"),
+            )
+        } else {
+            let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
+            for byte in self.secret_bytes().iter() {
+                tuple.serialize_element(&byte)?;
+            }
+            tuple.end()
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+// For `data` under some feature combinations (the unconditional panic below).
+#[allow(unused_variables)]
+#[cfg(all(
+    feature = "serde",
+    any(feature = "global-context", feature = "alloc")
+))]
+impl<'de> serde::Deserialize<'de> for Keypair {
+    fn deserialize<D: serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<Self, D::Error> {
+        if d.is_human_readable() {
+            d.deserialize_str(super::serde_util::FromStrVisitor::new(
+                "a hex string representing 32 byte Keypair",
+            ))
+        } else {
+            let visitor = super::serde_util::Tuple32Visitor::new(
+                "raw 32 bytes Keypair",
+                |data| {
+                    #[cfg(feature = "global-context")]
+                    let ctx = SECP256K1;
+
+                    #[cfg(all(
+                        not(feature = "global-context"),
+                        feature = "alloc"
+                    ))]
+                    let ctx = Secp256k1::signing_only();
+
+                    #[allow(clippy::needless_borrow)]
+                    Keypair::from_seckey_slice(&ctx, data)
+                },
+            );
+            d.deserialize_tuple(constants::SECRET_KEY_SIZE, visitor)
+        }
+    }
+}
+
+impl CPtr for Keypair {
+    type Target = ffi::Keypair;
+
+    fn as_c_ptr(&self) -> *const Self::Target {
+        &self.0
+    }
+
+    fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
+        &mut self.0
+    }
+}
+
+/// An x-only public key, used for verification of Taproot signatures and
+/// serialized according to BIP-340.
+///
+/// # Serde support
+///
+/// Implements de/serialization with the `serde` feature enabled. We treat the
+/// byte value as a tuple of 32 `u8`s for non-human-readable formats. This
+/// representation is optimal for for some formats (e.g. [`bincode`]) however
+/// other formats may be less optimal (e.g. [`cbor`]).
+///
+/// # Examples
+///
+/// Basic usage:
+///
+/// ```
+/// # #[cfg(all(feature = "rand", feature = "std"))] {
+/// use ecash_secp256k1::{rand, Keypair, Secp256k1, XOnlyPublicKey};
+///
+/// let secp = Secp256k1::new();
+/// let keypair = Keypair::new(&secp, &mut rand::thread_rng());
+/// let xonly = XOnlyPublicKey::from_keypair(&keypair);
+/// # }
+/// ```
+/// [`bincode`]: https://docs.rs/bincode
+/// [`cbor`]: https://docs.rs/cbor
+#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct XOnlyPublicKey(ffi::XOnlyPublicKey);
+impl_fast_comparisons!(XOnlyPublicKey);
+
+impl fmt::LowerHex for XOnlyPublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let ser = self.serialize();
+        for ch in &ser[..] {
+            write!(f, "{:02x}", *ch)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for XOnlyPublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
+impl fmt::Debug for XOnlyPublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
+impl str::FromStr for XOnlyPublicKey {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<XOnlyPublicKey, Error> {
+        let mut res = [0u8; constants::SCHNORR_PUBLIC_KEY_SIZE];
+        match from_hex(s, &mut res) {
+            Ok(constants::SCHNORR_PUBLIC_KEY_SIZE) => {
+                XOnlyPublicKey::from_byte_array(&res)
+            }
+            _ => Err(Error::InvalidPublicKey),
+        }
+    }
+}
+
+impl XOnlyPublicKey {
+    /// Obtains a raw const pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_ptr(&self) -> *const ffi::XOnlyPublicKey {
+        self.as_c_ptr()
+    }
+
+    /// Obtains a raw mutable pointer suitable for use with FFI functions.
+    #[inline]
+    #[deprecated(
+        since = "0.25.0",
+        note = "Use Self::as_mut_c_ptr if you need to access the FFI layer"
+    )]
+    pub fn as_mut_ptr(&mut self) -> *mut ffi::XOnlyPublicKey {
+        self.as_mut_c_ptr()
+    }
+
+    /// Returns the [`XOnlyPublicKey`] (and it's [`Parity`]) for `keypair`.
+    #[inline]
+    pub fn from_keypair(keypair: &Keypair) -> (XOnlyPublicKey, Parity) {
+        let mut pk_parity = 0;
+        unsafe {
+            let mut xonly_pk = ffi::XOnlyPublicKey::new();
+            let ret = ffi::secp256k1_keypair_xonly_pub(
+                ffi::secp256k1_context_no_precomp,
+                &mut xonly_pk,
+                &mut pk_parity,
+                keypair.as_c_ptr(),
+            );
+            debug_assert_eq!(ret, 1);
+            let parity = Parity::from_i32(pk_parity)
+                .expect("should not panic, pk_parity is 0 or 1");
+
+            (XOnlyPublicKey(xonly_pk), parity)
+        }
+    }
+
+    /// Creates a schnorr public key directly from a slice.
+    ///
+    /// # Errors
+    ///
+    /// Returns [`Error::InvalidPublicKey`] if the length of the data slice is
+    /// not 32 bytes or the slice does not represent a valid Secp256k1 point
+    /// x coordinate.
+    #[deprecated(since = "TBD", note = "Use `from_byte_array` instead.")]
+    #[inline]
+    pub fn from_slice(data: &[u8]) -> Result<XOnlyPublicKey, Error> {
+        match <[u8; constants::SCHNORR_PUBLIC_KEY_SIZE]>::try_from(data) {
+            Ok(data) => Self::from_byte_array(&data),
+            Err(_) => Err(InvalidPublicKey),
+        }
+    }
+
+    /// Creates a schnorr public key directly from a byte array.
+    ///
+    /// # Errors
+    ///
+    /// Returns [`Error::InvalidPublicKey`] if the array does not represent a
+    /// valid Secp256k1 point x coordinate.
+    #[inline]
+    pub fn from_byte_array(
+        data: &[u8; constants::SCHNORR_PUBLIC_KEY_SIZE],
+    ) -> Result<XOnlyPublicKey, Error> {
+        unsafe {
+            let mut pk = ffi::XOnlyPublicKey::new();
+            if ffi::secp256k1_xonly_pubkey_parse(
+                ffi::secp256k1_context_no_precomp,
+                &mut pk,
+                data.as_c_ptr(),
+            ) == 1
+            {
+                Ok(XOnlyPublicKey(pk))
+            } else {
+                Err(Error::InvalidPublicKey)
+            }
+        }
+    }
+
+    #[inline]
+    /// Serializes the key as a byte-encoded x coordinate value (32 bytes).
+    pub fn serialize(&self) -> [u8; constants::SCHNORR_PUBLIC_KEY_SIZE] {
+        let mut ret = [0u8; constants::SCHNORR_PUBLIC_KEY_SIZE];
+
+        unsafe {
+            let err = ffi::secp256k1_xonly_pubkey_serialize(
+                ffi::secp256k1_context_no_precomp,
+                ret.as_mut_c_ptr(),
+                self.as_c_ptr(),
+            );
+            debug_assert_eq!(err, 1);
+        }
+        ret
+    }
+
+    /// Tweaks an [`XOnlyPublicKey`] by adding the generator multiplied with the
+    /// given tweak to it.
+    ///
+    /// # Returns
+    ///
+    /// The newly tweaked key plus an opaque type representing the parity of the
+    /// tweaked key, this should be provided to `tweak_add_check` which can
+    /// be used to verify a tweak more efficiently than regenerating it and
+    /// checking equality.
+    ///
+    /// # Errors
+    ///
+    /// If the resulting key would be invalid.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{Keypair, Scalar, Secp256k1, XOnlyPublicKey};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let tweak = Scalar::random();
+    ///
+    /// let mut keypair = Keypair::new(&secp, &mut rand::thread_rng());
+    /// let (xonly, _parity) = keypair.x_only_public_key();
+    /// let tweaked = xonly
+    ///     .add_tweak(&secp, &tweak)
+    ///     .expect("Improbable to fail with a randomly generated tweak");
+    /// # }
+    /// ```
+    pub fn add_tweak<V: Verification>(
+        mut self,
+        secp: &Secp256k1<V>,
+        tweak: &Scalar,
+    ) -> Result<(XOnlyPublicKey, Parity), Error> {
+        let mut pk_parity = 0;
+        unsafe {
+            let mut pubkey = ffi::PublicKey::new();
+            let mut err = ffi::secp256k1_xonly_pubkey_tweak_add(
+                secp.ctx.as_ptr(),
+                &mut pubkey,
+                self.as_c_ptr(),
+                tweak.as_c_ptr(),
+            );
+            if err != 1 {
+                return Err(Error::InvalidTweak);
+            }
+
+            err = ffi::secp256k1_xonly_pubkey_from_pubkey(
+                secp.ctx.as_ptr(),
+                &mut self.0,
+                &mut pk_parity,
+                &pubkey,
+            );
+            if err == 0 {
+                return Err(Error::InvalidPublicKey);
+            }
+
+            let parity = Parity::from_i32(pk_parity)?;
+            Ok((self, parity))
+        }
+    }
+
+    /// Verifies that a tweak produced by [`XOnlyPublicKey::add_tweak`] was
+    /// computed correctly.
+    ///
+    /// Should be called on the original untweaked key. Takes the tweaked key
+    /// and output parity from [`XOnlyPublicKey::add_tweak`] as input.
+    ///
+    /// Currently this is not much more efficient than just recomputing the
+    /// tweak and checking equality. However, in future this API will
+    /// support batch verification, which is significantly faster, so it is
+    /// wise to design protocols with this in mind.
+    ///
+    /// # Returns
+    ///
+    /// True if tweak and check is successful, false otherwise.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(all(feature = "rand", feature = "std"))] {
+    /// use ecash_secp256k1::{Keypair, Scalar, Secp256k1};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let tweak = Scalar::random();
+    ///
+    /// let mut keypair = Keypair::new(&secp, &mut rand::thread_rng());
+    /// let (mut public_key, _) = keypair.x_only_public_key();
+    /// let original = public_key;
+    /// let (tweaked, parity) = public_key
+    ///     .add_tweak(&secp, &tweak)
+    ///     .expect("Improbable to fail with a randomly generated tweak");
+    /// assert!(original.tweak_add_check(&secp, &tweaked, parity, tweak));
+    /// # }
+    /// ```
+    pub fn tweak_add_check<V: Verification>(
+        &self,
+        secp: &Secp256k1<V>,
+        tweaked_key: &Self,
+        tweaked_parity: Parity,
+        tweak: Scalar,
+    ) -> bool {
+        let tweaked_ser = tweaked_key.serialize();
+        unsafe {
+            let err = ffi::secp256k1_xonly_pubkey_tweak_add_check(
+                secp.ctx.as_ptr(),
+                tweaked_ser.as_c_ptr(),
+                tweaked_parity.to_i32(),
+                &self.0,
+                tweak.as_c_ptr(),
+            );
+
+            err == 1
+        }
+    }
+
+    /// Returns the [`PublicKey`] for this [`XOnlyPublicKey`].
+    ///
+    /// This is equivalent to using [`PublicKey::from_xonly_and_parity(self,
+    /// parity)`].
+    #[inline]
+    pub fn public_key(&self, parity: Parity) -> PublicKey {
+        PublicKey::from_x_only_public_key(*self, parity)
+    }
+
+    /// Checks that `sig` is a valid schnorr signature for `msg` using this
+    /// public key.
+    pub fn verify<C: Verification>(
+        &self,
+        secp: &Secp256k1<C>,
+        msg: &[u8],
+        sig: &schnorr::Signature,
+    ) -> Result<(), Error> {
+        secp.verify_schnorr(sig, msg, self)
+    }
+}
+
+/// Represents the parity passed between FFI function calls.
+#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
+pub enum Parity {
+    /// Even parity.
+    Even = 0,
+    /// Odd parity.
+    Odd = 1,
+}
+
+impl Parity {
+    /// Converts parity into an integer (byte) value.
+    ///
+    /// This returns `0` for even parity and `1` for odd parity.
+    pub fn to_u8(self) -> u8 {
+        self as u8
+    }
+
+    /// Converts parity into an integer value.
+    ///
+    /// This returns `0` for even parity and `1` for odd parity.
+    pub fn to_i32(self) -> i32 {
+        self as i32
+    }
+
+    /// Constructs a [`Parity`] from a byte.
+    ///
+    /// The only allowed values are `0` meaning even parity and `1` meaning odd.
+    /// Other values result in error being returned.
+    pub fn from_u8(parity: u8) -> Result<Parity, InvalidParityValue> {
+        Parity::from_i32(parity.into())
+    }
+
+    /// Constructs a [`Parity`] from a signed integer.
+    ///
+    /// The only allowed values are `0` meaning even parity and `1` meaning odd.
+    /// Other values result in error being returned.
+    pub fn from_i32(parity: i32) -> Result<Parity, InvalidParityValue> {
+        match parity {
+            0 => Ok(Parity::Even),
+            1 => Ok(Parity::Odd),
+            _ => Err(InvalidParityValue(parity)),
+        }
+    }
+}
+
+/// `Even` for `0`, `Odd` for `1`, error for anything else
+impl TryFrom<i32> for Parity {
+    type Error = InvalidParityValue;
+
+    fn try_from(parity: i32) -> Result<Self, Self::Error> {
+        Self::from_i32(parity)
+    }
+}
+
+/// `Even` for `0`, `Odd` for `1`, error for anything else
+impl TryFrom<u8> for Parity {
+    type Error = InvalidParityValue;
+
+    fn try_from(parity: u8) -> Result<Self, Self::Error> {
+        Self::from_u8(parity)
+    }
+}
+
+/// The conversion returns `0` for even parity and `1` for odd.
+impl From<Parity> for i32 {
+    fn from(parity: Parity) -> i32 {
+        parity.to_i32()
+    }
+}
+
+/// The conversion returns `0` for even parity and `1` for odd.
+impl From<Parity> for u8 {
+    fn from(parity: Parity) -> u8 {
+        parity.to_u8()
+    }
+}
+
+/// Returns even parity if the operands are equal, odd otherwise.
+impl BitXor for Parity {
+    type Output = Parity;
+
+    fn bitxor(self, rhs: Parity) -> Self::Output {
+        // This works because Parity has only two values (i.e. only 1 bit of
+        // information).
+        if self == rhs {
+            Parity::Even // 1^1==0 and 0^0==0
+        } else {
+            Parity::Odd // 1^0==1 and 0^1==1
+        }
+    }
+}
+
+/// Error returned when conversion from an integer to `Parity` fails.
+//
+// Note that we don't allow inspecting the value because we may change the type.
+// Yes, this comment is intentionally NOT doc comment.
+// Too many derives for compatibility with current Error type.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
+pub struct InvalidParityValue(i32);
+
+impl fmt::Display for InvalidParityValue {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "invalid value {} for Parity - must be 0 or 1", self.0)
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for InvalidParityValue {}
+
+impl From<InvalidParityValue> for Error {
+    fn from(error: InvalidParityValue) -> Self {
+        Error::InvalidParityValue(error)
+    }
+}
+
+/// The parity is serialized as `u8` - `0` for even, `1` for odd.
+#[cfg(feature = "serde")]
+impl serde::Serialize for Parity {
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+        s.serialize_u8(self.to_u8())
+    }
+}
+
+/// The parity is deserialized as `u8` - `0` for even, `1` for odd.
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for Parity {
+    fn deserialize<D: serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<Self, D::Error> {
+        struct Visitor;
+
+        impl<'de> serde::de::Visitor<'de> for Visitor {
+            type Value = Parity;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("8-bit integer (byte) with value 0 or 1")
+            }
+
+            fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
+            where
+                E: serde::de::Error,
+            {
+                use serde::de::Unexpected;
+
+                Parity::from_u8(v).map_err(|_| {
+                    E::invalid_value(Unexpected::Unsigned(v.into()), &"0 or 1")
+                })
+            }
+        }
+
+        d.deserialize_u8(Visitor)
+    }
+}
+
+impl CPtr for XOnlyPublicKey {
+    type Target = ffi::XOnlyPublicKey;
+
+    fn as_c_ptr(&self) -> *const Self::Target {
+        &self.0
+    }
+
+    fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
+        &mut self.0
+    }
+}
+
+/// Creates a new schnorr public key from a FFI x-only public key.
+impl From<ffi::XOnlyPublicKey> for XOnlyPublicKey {
+    #[inline]
+    fn from(pk: ffi::XOnlyPublicKey) -> XOnlyPublicKey {
+        XOnlyPublicKey(pk)
+    }
+}
+
+impl From<PublicKey> for XOnlyPublicKey {
+    fn from(src: PublicKey) -> XOnlyPublicKey {
+        unsafe {
+            let mut pk = ffi::XOnlyPublicKey::new();
+            assert_eq!(
+                1,
+                ffi::secp256k1_xonly_pubkey_from_pubkey(
+                    ffi::secp256k1_context_no_precomp,
+                    &mut pk,
+                    ptr::null_mut(),
+                    src.as_c_ptr(),
+                )
+            );
+            XOnlyPublicKey(pk)
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for XOnlyPublicKey {
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+        if s.is_human_readable() {
+            s.collect_str(self)
+        } else {
+            let mut tuple =
+                s.serialize_tuple(constants::SCHNORR_PUBLIC_KEY_SIZE)?;
+            for byte in self.serialize().iter() {
+                tuple.serialize_element(&byte)?;
+            }
+            tuple.end()
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for XOnlyPublicKey {
+    fn deserialize<D: serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<Self, D::Error> {
+        if d.is_human_readable() {
+            d.deserialize_str(super::serde_util::FromStrVisitor::new(
+                "a hex string representing 32 byte schnorr public key",
+            ))
+        } else {
+            let visitor = super::serde_util::Tuple32Visitor::new(
+                "raw 32 bytes schnorr public key",
+                XOnlyPublicKey::from_slice,
+            );
+            d.deserialize_tuple(constants::SCHNORR_PUBLIC_KEY_SIZE, visitor)
+        }
+    }
+}
+
+#[cfg(test)]
+#[allow(unused_imports)]
+mod test {
+    use core::str::FromStr;
+
+    #[cfg(not(secp256k1_fuzz))]
+    use hex_lit::hex;
+    #[cfg(feature = "rand")]
+    use rand::{self, rngs::mock::StepRng, RngCore};
+    use serde_test::{Configure, Token};
+    #[cfg(target_arch = "wasm32")]
+    use wasm_bindgen_test::wasm_bindgen_test as test;
+
+    use super::{
+        Keypair, Parity, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey, *,
+    };
+    use crate::Error::{InvalidPublicKey, InvalidSecretKey};
+    use crate::{constants, from_hex, to_hex, Scalar};
+
+    #[test]
+    fn skey_from_slice() {
+        let sk = SecretKey::from_slice(&[1; 31]);
+        assert_eq!(sk, Err(InvalidSecretKey));
+
+        let sk = SecretKey::from_slice(&[1; 32]);
+        assert!(sk.is_ok());
+    }
+
+    #[test]
+    fn pubkey_from_slice() {
+        assert_eq!(PublicKey::from_slice(&[]), Err(InvalidPublicKey));
+        assert_eq!(PublicKey::from_slice(&[1, 2, 3]), Err(InvalidPublicKey));
+
+        let uncompressed = PublicKey::from_slice(&[
+            4, 54, 57, 149, 239, 162, 148, 175, 246, 254, 239, 75, 154, 152,
+            10, 82, 234, 224, 85, 220, 40, 100, 57, 121, 30, 162, 94, 156, 135,
+            67, 74, 49, 179, 57, 236, 53, 162, 124, 149, 144, 168, 77, 74, 30,
+            72, 211, 229, 110, 111, 55, 96, 193, 86, 227, 183, 152, 195, 155,
+            51, 247, 123, 113, 60, 228, 188,
+        ]);
+        assert!(uncompressed.is_ok());
+
+        let compressed = PublicKey::from_slice(&[
+            3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248,
+            140, 11, 3, 51, 41, 111, 180, 110, 143, 114, 134, 88, 73, 198, 174,
+            52, 184, 78,
+        ]);
+        assert!(compressed.is_ok());
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn keypair_slice_round_trip() {
+        let s = Secp256k1::new();
+
+        let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
+        assert_eq!(SecretKey::from_slice(&sk1[..]), Ok(sk1));
+        assert_eq!(PublicKey::from_slice(&pk1.serialize()[..]), Ok(pk1));
+        assert_eq!(
+            PublicKey::from_slice(&pk1.serialize_uncompressed()[..]),
+            Ok(pk1)
+        );
+    }
+
+    #[test]
+    #[cfg(all(feature = "std", not(secp256k1_fuzz)))]
+    fn erased_keypair_is_valid() {
+        let s = Secp256k1::new();
+        let kp =
+            Keypair::from_seckey_slice(&s, &[1u8; constants::SECRET_KEY_SIZE])
+                .expect("valid secret key");
+        let mut kp2 = kp;
+        kp2.non_secure_erase();
+        assert!(kp.eq_fast_unstable(&kp2));
+    }
+
+    #[test]
+    #[rustfmt::skip]
+    fn invalid_secret_key() {
+        // Zero
+        assert_eq!(SecretKey::from_slice(&[0; 32]), Err(InvalidSecretKey));
+        assert_eq!(
+            SecretKey::from_str(
+                "00000000000000000000000000000000000000000000000000000000000000\
+                 00"
+            ),
+            Err(InvalidSecretKey)
+        );
+        // -1
+        assert_eq!(SecretKey::from_slice(&[0xff; 32]), Err(InvalidSecretKey));
+        // Top of range
+        assert!(SecretKey::from_slice(&[
+            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
+            0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
+            0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x40,
+        ]).is_ok());
+        // One past top of range
+        assert!(SecretKey::from_slice(&[
+            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
+            0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
+            0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41,
+        ]).is_err());
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "alloc"))]
+    fn test_out_of_range() {
+        struct BadRng(u8);
+        impl RngCore for BadRng {
+            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
+            // group order, then decrement with repeated calls
+            // until it returns a valid key
+            fn fill_bytes(&mut self, data: &mut [u8]) {
+                #[rustfmt::skip]
+                let group_order: [u8; 32] = [
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+                    0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
+                    0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41];
+                assert_eq!(data.len(), 32);
+                data.copy_from_slice(&group_order[..]);
+                data[31] = self.0;
+                self.0 -= 1;
+            }
+
+            fn try_fill_bytes(
+                &mut self,
+                dest: &mut [u8],
+            ) -> Result<(), rand::Error> {
+                self.fill_bytes(dest);
+                Ok(())
+            }
+        }
+
+        let s = Secp256k1::new();
+        s.generate_keypair(&mut BadRng(0xff));
+    }
+
+    #[test]
+    fn test_pubkey_from_bad_slice() {
+        // Bad sizes
+        assert_eq!(
+            PublicKey::from_slice(&[0; constants::PUBLIC_KEY_SIZE - 1]),
+            Err(InvalidPublicKey)
+        );
+        assert_eq!(
+            PublicKey::from_slice(&[0; constants::PUBLIC_KEY_SIZE + 1]),
+            Err(InvalidPublicKey)
+        );
+        assert_eq!(
+            PublicKey::from_slice(
+                &[0; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE - 1]
+            ),
+            Err(InvalidPublicKey)
+        );
+        assert_eq!(
+            PublicKey::from_slice(
+                &[0; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE + 1]
+            ),
+            Err(InvalidPublicKey)
+        );
+
+        // Bad parse
+        assert_eq!(
+            PublicKey::from_slice(
+                &[0xff; constants::UNCOMPRESSED_PUBLIC_KEY_SIZE]
+            ),
+            Err(InvalidPublicKey)
+        );
+        assert_eq!(
+            PublicKey::from_slice(&[0x55; constants::PUBLIC_KEY_SIZE]),
+            Err(InvalidPublicKey)
+        );
+        assert_eq!(PublicKey::from_slice(&[]), Err(InvalidPublicKey));
+    }
+
+    #[test]
+    fn test_seckey_from_bad_slice() {
+        // Bad sizes
+        assert_eq!(
+            SecretKey::from_slice(&[0; constants::SECRET_KEY_SIZE - 1]),
+            Err(InvalidSecretKey)
+        );
+        assert_eq!(
+            SecretKey::from_slice(&[0; constants::SECRET_KEY_SIZE + 1]),
+            Err(InvalidSecretKey)
+        );
+        // Bad parse
+        assert_eq!(
+            SecretKey::from_slice(&[0xff; constants::SECRET_KEY_SIZE]),
+            Err(InvalidSecretKey)
+        );
+        assert_eq!(
+            SecretKey::from_slice(&[0x00; constants::SECRET_KEY_SIZE]),
+            Err(InvalidSecretKey)
+        );
+        assert_eq!(SecretKey::from_slice(&[]), Err(InvalidSecretKey));
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "alloc", not(feature = "hashes")))]
+    fn test_debug_output() {
+        let s = Secp256k1::new();
+        let (sk, _) = s.generate_keypair(&mut StepRng::new(1, 1));
+
+        assert_eq!(
+            &format!("{:?}", sk),
+            "<secret key; enable `hashes` feature of `secp256k1` to display \
+             fingerprint>"
+        );
+
+        let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2];
+        assert_eq!(
+            to_hex(&sk[..], &mut buf).unwrap(),
+            "0100000000000000020000000000000003000000000000000400000000000000"
+        );
+    }
+
+    #[test]
+    #[cfg(feature = "alloc")]
+    fn test_display_output() {
+        #[rustfmt::skip]
+        static SK_BYTES: [u8; 32] = [
+            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+            0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+            0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+        ];
+
+        #[cfg(not(secp256k1_fuzz))]
+        let s = Secp256k1::signing_only();
+        let sk = SecretKey::from_slice(&SK_BYTES).expect("sk");
+
+        // In fuzzing mode secret->public key derivation is different, so
+        // hard-code the expected result.
+        #[cfg(not(secp256k1_fuzz))]
+        let pk = PublicKey::from_secret_key(&s, &sk);
+        #[cfg(secp256k1_fuzz)]
+        let pk = PublicKey::from_slice(&[
+            0x02, 0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f, 0x1c, 0x97,
+            0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d, 0x06, 0x83, 0x7f, 0x30, 0xaa,
+            0x0c, 0xd0, 0x54, 0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
+        ])
+        .expect("pk");
+
+        assert_eq!(
+            sk.display_secret().to_string(),
+            "01010101010101010001020304050607ffff0000ffff00006363636363636363"
+        );
+        assert_eq!(
+            SecretKey::from_str(
+                "01010101010101010001020304050607ffff0000ffff000063636363636363\
+                 63"
+            )
+            .unwrap(),
+            sk
+        );
+        assert_eq!(
+            pk.to_string(),
+            "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166"
+        );
+        assert_eq!(
+            PublicKey::from_str(
+                "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91dd\
+                 d166"
+            )
+            .unwrap(),
+            pk
+        );
+        assert_eq!(
+            PublicKey::from_str(
+                "0418845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91dd\
+                 d16684B84DB303A340CD7D6823EE88174747D12A67D2F8F2F9BA40846EE5EE\
+                 7A44F6"
+            )
+            .unwrap(),
+            pk
+        );
+
+        assert!(SecretKey::from_str(
+            "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+        )
+        .is_err());
+        assert!(SecretKey::from_str(
+            "01010101010101010001020304050607ffff0000ffff0000636363636363636363"
+        )
+        .is_err());
+        assert!(SecretKey::from_str(
+            "01010101010101010001020304050607ffff0000ffff0000636363636363636"
+        )
+        .is_err());
+        assert!(SecretKey::from_str(
+            "01010101010101010001020304050607ffff0000ffff000063636363636363"
+        )
+        .is_err());
+        assert!(SecretKey::from_str(
+            "01010101010101010001020304050607ffff0000ffff000063636363636363xx"
+        )
+        .is_err());
+        assert!(PublicKey::from_str(
+            "03000000000000000000000000000000000000000000000000000000000000000
+             00"
+        )
+        .is_err());
+        assert!(PublicKey::from_str(
+            "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166\
+             01"
+        )
+        .is_err());
+        assert!(PublicKey::from_str(
+            "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd16"
+        )
+        .is_err());
+        assert!(PublicKey::from_str(
+            "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1"
+        )
+        .is_err());
+        assert!(PublicKey::from_str(
+            "xx0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1"
+        )
+        .is_err());
+
+        let long_str = "a".repeat(1024 * 1024);
+        assert!(SecretKey::from_str(&long_str).is_err());
+        assert!(PublicKey::from_str(&long_str).is_err());
+    }
+
+    #[test]
+    // In fuzzing mode the Y coordinate is expected to match the X, so this
+    // test uses invalid public keys.
+    #[cfg(not(secp256k1_fuzz))]
+    #[cfg(all(feature = "alloc", feature = "rand"))]
+    fn test_pubkey_serialize() {
+        let s = Secp256k1::new();
+        let (_, pk1) = s.generate_keypair(&mut StepRng::new(1, 1));
+        assert_eq!(
+            &pk1.serialize_uncompressed()[..],
+            &[
+                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()[..],
+            &[
+                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]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn tweak_add_arbitrary_data() {
+        let s = Secp256k1::new();
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+        assert_eq!(PublicKey::from_secret_key(&s, &sk), pk); // Sanity check.
+
+        // TODO: This would be better tested with a _lot_ of different tweaks.
+        let tweak = Scalar::random();
+
+        let tweaked_sk = sk.add_tweak(&tweak).unwrap();
+        assert_ne!(sk, tweaked_sk); // Make sure we did something.
+        let tweaked_pk = pk.add_exp_tweak(&s, &tweak).unwrap();
+        assert_ne!(pk, tweaked_pk);
+
+        assert_eq!(PublicKey::from_secret_key(&s, &tweaked_sk), tweaked_pk);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn tweak_add_zero() {
+        let s = Secp256k1::new();
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+
+        let tweak = Scalar::ZERO;
+
+        let tweaked_sk = sk.add_tweak(&tweak).unwrap();
+        assert_eq!(sk, tweaked_sk); // Tweak by zero does nothing.
+        let tweaked_pk = pk.add_exp_tweak(&s, &tweak).unwrap();
+        assert_eq!(pk, tweaked_pk);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn tweak_mul_arbitrary_data() {
+        let s = Secp256k1::new();
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+        assert_eq!(PublicKey::from_secret_key(&s, &sk), pk); // Sanity check.
+
+        // TODO: This would be better tested with a _lot_ of different tweaks.
+        let tweak = Scalar::random();
+
+        let tweaked_sk = sk.mul_tweak(&tweak).unwrap();
+        assert_ne!(sk, tweaked_sk); // Make sure we did something.
+        let tweaked_pk = pk.mul_tweak(&s, &tweak).unwrap();
+        assert_ne!(pk, tweaked_pk);
+
+        assert_eq!(PublicKey::from_secret_key(&s, &tweaked_sk), tweaked_pk);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn tweak_mul_zero() {
+        let s = Secp256k1::new();
+        let (sk, _) = s.generate_keypair(&mut rand::thread_rng());
+
+        let tweak = Scalar::ZERO;
+        assert!(sk.mul_tweak(&tweak).is_err())
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn test_negation() {
+        let s = Secp256k1::new();
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+
+        assert_eq!(PublicKey::from_secret_key(&s, &sk), pk); // Sanity check.
+
+        let neg = sk.negate();
+        assert_ne!(sk, neg);
+        let back_sk = neg.negate();
+        assert_eq!(sk, back_sk);
+
+        let neg = pk.negate(&s);
+        assert_ne!(pk, neg);
+        let back_pk = neg.negate(&s);
+        assert_eq!(pk, back_pk);
+
+        assert_eq!(PublicKey::from_secret_key(&s, &back_sk), pk);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn pubkey_hash() {
+        use std::collections::hash_map::DefaultHasher;
+        use std::collections::HashSet;
+        use std::hash::{Hash, Hasher};
+
+        fn hash<T: Hash>(t: &T) -> u64 {
+            let mut s = DefaultHasher::new();
+            t.hash(&mut s);
+            s.finish()
+        }
+
+        let s = Secp256k1::new();
+        let mut set = HashSet::new();
+        const COUNT: usize = 1024;
+        for _ in 0..COUNT {
+            let (_, pk) = s.generate_keypair(&mut rand::thread_rng());
+            let hash = hash(&pk);
+            assert!(!set.contains(&hash));
+            set.insert(hash);
+        }
+        assert_eq!(set.len(), COUNT);
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))]
+    fn pubkey_combine() {
+        let compressed1 = PublicKey::from_slice(&hex!(
+            "0241cc121c419921942add6db6482fb36243faf83317c866d2a28d8c6d7089f7ba"
+        ))
+        .unwrap();
+        let compressed2 = PublicKey::from_slice(&hex!(
+            "02e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443"
+        ))
+        .unwrap();
+        let exp_sum = PublicKey::from_slice(&hex!(
+            "0384526253c27c7aef56c7b71a5cd25bebb66dddda437826defc5b2568bde81f07"
+        ))
+        .unwrap();
+
+        let sum1 = compressed1.combine(&compressed2);
+        assert!(sum1.is_ok());
+        let sum2 = compressed2.combine(&compressed1);
+        assert!(sum2.is_ok());
+        assert_eq!(sum1, sum2);
+        assert_eq!(sum1.unwrap(), exp_sum);
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))]
+    fn pubkey_combine_keys() {
+        let compressed1 = PublicKey::from_slice(&hex!(
+            "0241cc121c419921942add6db6482fb36243faf83317c866d2a28d8c6d7089f7ba"
+        ))
+        .unwrap();
+        let compressed2 = PublicKey::from_slice(&hex!(
+            "02e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443"
+        ))
+        .unwrap();
+        let compressed3 = PublicKey::from_slice(&hex!(
+            "03e74897d8644eb3e5b391ca2ab257aec2080f4d1a95cad57e454e47f021168eb0"
+        ))
+        .unwrap();
+        let exp_sum = PublicKey::from_slice(&hex!(
+            "0252d73a47f66cf341e5651542f0348f452b7c793af62a6d8bff75ade703a451ad"
+        ))
+        .unwrap();
+
+        let sum1 = PublicKey::combine_keys(&[
+            &compressed1,
+            &compressed2,
+            &compressed3,
+        ]);
+        assert!(sum1.is_ok());
+        let sum2 = PublicKey::combine_keys(&[
+            &compressed1,
+            &compressed2,
+            &compressed3,
+        ]);
+        assert!(sum2.is_ok());
+        assert_eq!(sum1, sum2);
+        assert_eq!(sum1.unwrap(), exp_sum);
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))]
+    fn pubkey_combine_keys_empty_slice() {
+        assert!(PublicKey::combine_keys(&[]).is_err());
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn create_pubkey_combine() {
+        let s = Secp256k1::new();
+
+        let (sk1, pk1) = s.generate_keypair(&mut rand::thread_rng());
+        let (sk2, pk2) = s.generate_keypair(&mut rand::thread_rng());
+
+        let sum1 = pk1.combine(&pk2);
+        assert!(sum1.is_ok());
+        let sum2 = pk2.combine(&pk1);
+        assert!(sum2.is_ok());
+        assert_eq!(sum1, sum2);
+
+        let tweaked = sk1.add_tweak(&Scalar::from(sk2)).unwrap();
+        let sksum = PublicKey::from_secret_key(&s, &tweaked);
+        assert_eq!(Ok(sksum), sum1);
+    }
+
+    #[cfg(not(secp256k1_fuzz))]
+    #[test]
+    #[allow(clippy::nonminimal_bool)]
+    fn pubkey_equal() {
+        let pk1 = PublicKey::from_slice(&hex!(
+            "0241cc121c419921942add6db6482fb36243faf83317c866d2a28d8c6d7089f7ba"
+        ))
+        .unwrap();
+        let pk2 = pk1;
+        let pk3 = PublicKey::from_slice(&hex!(
+            "02e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443"
+        ))
+        .unwrap();
+
+        assert_eq!(pk1, pk2);
+        assert!(pk1 <= pk2);
+        assert!(pk2 <= pk1);
+        assert!(!(pk2 < pk1));
+        assert!(!(pk1 < pk2));
+
+        assert!(pk3 > pk1);
+        assert!(pk1 < pk3);
+        assert!(pk3 >= pk1);
+        assert!(pk1 <= pk3);
+    }
+
+    #[test]
+    #[cfg(all(feature = "serde", feature = "alloc"))]
+    fn test_serde() {
+        use serde_test::{assert_tokens, Configure, Token};
+        #[rustfmt::skip]
+        static SK_BYTES: [u8; 32] = [
+            1, 1, 1, 1, 1, 1, 1, 1,
+            0, 1, 2, 3, 4, 5, 6, 7,
+            0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
+            99, 99, 99, 99, 99, 99, 99, 99
+        ];
+        static SK_STR: &str =
+            "01010101010101010001020304050607ffff0000ffff00006363636363636363";
+
+        #[cfg(secp256k1_fuzz)]
+        #[rustfmt::skip]
+        static PK_BYTES: [u8; 33] = [
+            0x02,
+            0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
+            0x1c, 0x97, 0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d,
+            0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c, 0xd0, 0x54,
+            0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
+        ];
+        static PK_STR: &str =
+            "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166\
+            ";
+
+        #[cfg(not(secp256k1_fuzz))]
+        let s = Secp256k1::new();
+        let sk = SecretKey::from_slice(&SK_BYTES).unwrap();
+
+        // In fuzzing mode secret->public key derivation is different, so
+        // hard-code the expected result.
+        #[cfg(not(secp256k1_fuzz))]
+        let pk = PublicKey::from_secret_key(&s, &sk);
+        #[cfg(secp256k1_fuzz)]
+        let pk = PublicKey::from_slice(&PK_BYTES).expect("pk");
+
+        assert_tokens(
+            &sk.compact(),
+            &[
+                Token::Tuple { len: 32 },
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(0),
+                Token::U8(1),
+                Token::U8(2),
+                Token::U8(3),
+                Token::U8(4),
+                Token::U8(5),
+                Token::U8(6),
+                Token::U8(7),
+                Token::U8(0xff),
+                Token::U8(0xff),
+                Token::U8(0),
+                Token::U8(0),
+                Token::U8(0xff),
+                Token::U8(0xff),
+                Token::U8(0),
+                Token::U8(0),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::TupleEnd,
+            ],
+        );
+
+        assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
+        assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
+        assert_tokens(&sk.readable(), &[Token::String(SK_STR)]);
+
+        assert_tokens(
+            &pk.compact(),
+            &[
+                Token::Tuple { len: 33 },
+                Token::U8(0x02),
+                Token::U8(0x18),
+                Token::U8(0x84),
+                Token::U8(0x57),
+                Token::U8(0x81),
+                Token::U8(0xf6),
+                Token::U8(0x31),
+                Token::U8(0xc4),
+                Token::U8(0x8f),
+                Token::U8(0x1c),
+                Token::U8(0x97),
+                Token::U8(0x09),
+                Token::U8(0xe2),
+                Token::U8(0x30),
+                Token::U8(0x92),
+                Token::U8(0x06),
+                Token::U8(0x7d),
+                Token::U8(0x06),
+                Token::U8(0x83),
+                Token::U8(0x7f),
+                Token::U8(0x30),
+                Token::U8(0xaa),
+                Token::U8(0x0c),
+                Token::U8(0xd0),
+                Token::U8(0x54),
+                Token::U8(0x4a),
+                Token::U8(0xc8),
+                Token::U8(0x87),
+                Token::U8(0xfe),
+                Token::U8(0x91),
+                Token::U8(0xdd),
+                Token::U8(0xd1),
+                Token::U8(0x66),
+                Token::TupleEnd,
+            ],
+        );
+
+        assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
+        assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
+        assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn test_tweak_add_then_tweak_add_check() {
+        let s = Secp256k1::new();
+
+        // TODO: 10 times is arbitrary, we should test this a _lot_ of times.
+        for _ in 0..10 {
+            let tweak = Scalar::random();
+
+            let kp = Keypair::new(&s, &mut rand::thread_rng());
+            let (xonly, _) = XOnlyPublicKey::from_keypair(&kp);
+
+            let tweaked_kp = kp
+                .add_xonly_tweak(&s, &tweak)
+                .expect("keypair tweak add failed");
+            let (tweaked_xonly, parity) = xonly
+                .add_tweak(&s, &tweak)
+                .expect("xonly pubkey tweak failed");
+
+            let (want_tweaked_xonly, tweaked_kp_parity) =
+                XOnlyPublicKey::from_keypair(&tweaked_kp);
+
+            assert_eq!(tweaked_xonly, want_tweaked_xonly);
+            assert_eq!(parity, tweaked_kp_parity);
+
+            assert!(xonly.tweak_add_check(&s, &tweaked_xonly, parity, tweak));
+        }
+    }
+
+    #[test]
+    fn test_from_key_pubkey() {
+        let kpk1 = PublicKey::from_str(
+            "02e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443\
+            ",
+        )
+        .unwrap();
+        let kpk2 = PublicKey::from_str(
+            "0384526253c27c7aef56c7b71a5cd25bebb66dddda437826defc5b2568bde81f07\
+            ",
+        )
+        .unwrap();
+
+        let pk1 = XOnlyPublicKey::from(kpk1);
+        let pk2 = XOnlyPublicKey::from(kpk2);
+
+        assert_eq!(pk1.serialize()[..], kpk1.serialize()[1..]);
+        assert_eq!(pk2.serialize()[..], kpk2.serialize()[1..]);
+    }
+
+    #[test]
+    #[cfg(all(feature = "global-context", feature = "serde"))]
+    fn test_serde_keypair() {
+        use serde::{Deserialize, Deserializer, Serialize, Serializer};
+        use serde_test::{assert_tokens, Configure, Token};
+
+        use crate::key::Keypair;
+        use crate::SECP256K1;
+
+        #[rustfmt::skip]
+        static SK_BYTES: [u8; 32] = [
+            1, 1, 1, 1, 1, 1, 1, 1,
+            0, 1, 2, 3, 4, 5, 6, 7,
+            0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
+            99, 99, 99, 99, 99, 99, 99, 99
+        ];
+        static SK_STR: &str =
+            "01010101010101010001020304050607ffff0000ffff00006363636363636363";
+
+        let sk = Keypair::from_seckey_slice(SECP256K1, &SK_BYTES).unwrap();
+
+        assert_tokens(
+            &sk.compact(),
+            &[
+                Token::Tuple { len: 32 },
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(1),
+                Token::U8(0),
+                Token::U8(1),
+                Token::U8(2),
+                Token::U8(3),
+                Token::U8(4),
+                Token::U8(5),
+                Token::U8(6),
+                Token::U8(7),
+                Token::U8(0xff),
+                Token::U8(0xff),
+                Token::U8(0),
+                Token::U8(0),
+                Token::U8(0xff),
+                Token::U8(0xff),
+                Token::U8(0),
+                Token::U8(0),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::U8(99),
+                Token::TupleEnd,
+            ],
+        );
+
+        assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
+        assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
+        assert_tokens(&sk.readable(), &[Token::String(SK_STR)]);
+    }
+
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn keys() -> (SecretKey, PublicKey, Keypair, XOnlyPublicKey) {
+        let secp = Secp256k1::new();
+
+        #[rustfmt::skip]
+        static SK_BYTES: [u8; 32] = [
+            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+            0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+            0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+        ];
+
+        #[rustfmt::skip]
+        static PK_BYTES: [u8; 32] = [
+            0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
+            0x1c, 0x97, 0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d,
+            0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c, 0xd0, 0x54,
+            0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66
+        ];
+
+        let mut pk_bytes = [0u8; 33];
+        pk_bytes[0] = 0x02; // Use positive Y co-ordinate.
+        pk_bytes[1..].clone_from_slice(&PK_BYTES);
+
+        let sk =
+            SecretKey::from_slice(&SK_BYTES).expect("failed to parse sk bytes");
+        let pk = PublicKey::from_slice(&pk_bytes)
+            .expect("failed to create pk from iterator");
+        let kp = Keypair::from_secret_key(&secp, &sk);
+        let xonly = XOnlyPublicKey::from_slice(&PK_BYTES)
+            .expect("failed to get xonly from slice");
+
+        (sk, pk, kp, xonly)
+    }
+
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn convert_public_key_to_xonly_public_key() {
+        let (_sk, pk, _kp, want) = keys();
+        let (got, parity) = pk.x_only_public_key();
+
+        assert_eq!(parity, Parity::Even);
+        assert_eq!(got, want)
+    }
+
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn convert_secret_key_to_public_key() {
+        let secp = Secp256k1::new();
+
+        let (sk, want, _kp, _xonly) = keys();
+        let got = sk.public_key(&secp);
+
+        assert_eq!(got, want)
+    }
+
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn convert_secret_key_to_x_only_public_key() {
+        let secp = Secp256k1::new();
+
+        let (sk, _pk, _kp, want) = keys();
+        let (got, parity) = sk.x_only_public_key(&secp);
+
+        assert_eq!(parity, Parity::Even);
+        assert_eq!(got, want)
+    }
+
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn convert_keypair_to_public_key() {
+        let (_sk, want, kp, _xonly) = keys();
+        let got = kp.public_key();
+
+        assert_eq!(got, want)
+    }
+
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn convert_keypair_to_x_only_public_key() {
+        let (_sk, _pk, kp, want) = keys();
+        let (got, parity) = kp.x_only_public_key();
+
+        assert_eq!(parity, Parity::Even);
+        assert_eq!(got, want)
+    }
+
+    // SecretKey -> Keypair -> SecretKey
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn roundtrip_secret_key_via_keypair() {
+        let secp = Secp256k1::new();
+        let (sk, _pk, _kp, _xonly) = keys();
+
+        let kp = sk.keypair(&secp);
+        let back = kp.secret_key();
+
+        assert_eq!(back, sk)
+    }
+
+    // Keypair -> SecretKey -> Keypair
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn roundtrip_keypair_via_secret_key() {
+        let secp = Secp256k1::new();
+        let (_sk, _pk, kp, _xonly) = keys();
+
+        let sk = kp.secret_key();
+        let back = sk.keypair(&secp);
+
+        assert_eq!(back, kp)
+    }
+
+    // XOnlyPublicKey -> PublicKey -> XOnlyPublicKey
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn roundtrip_x_only_public_key_via_public_key() {
+        let (_sk, _pk, _kp, xonly) = keys();
+
+        let pk = xonly.public_key(Parity::Even);
+        let (back, parity) = pk.x_only_public_key();
+
+        assert_eq!(parity, Parity::Even);
+        assert_eq!(back, xonly)
+    }
+
+    // PublicKey -> XOnlyPublicKey -> PublicKey
+    #[test]
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    fn roundtrip_public_key_via_x_only_public_key() {
+        let (_sk, pk, _kp, _xonly) = keys();
+
+        let (xonly, parity) = pk.x_only_public_key();
+        let back = xonly.public_key(parity);
+
+        assert_eq!(back, pk)
+    }
+
+    #[test]
+    fn public_key_from_x_only_public_key_and_odd_parity() {
+        let s =
+            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166";
+        let mut want = String::from("03");
+        want.push_str(s);
+
+        let xonly = XOnlyPublicKey::from_str(s)
+            .expect("failed to parse xonly pubkey string");
+        let pk = xonly.public_key(Parity::Odd);
+        let got = format!("{}", pk);
+
+        assert_eq!(got, want)
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))]
+    #[cfg(all(feature = "global-context", feature = "serde"))]
+    fn test_serde_x_only_pubkey() {
+        use serde_test::{assert_tokens, Configure, Token};
+
+        #[rustfmt::skip]
+        static SK_BYTES: [u8; 32] = [
+            1, 1, 1, 1, 1, 1, 1, 1,
+            0, 1, 2, 3, 4, 5, 6, 7,
+            0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
+            99, 99, 99, 99, 99, 99, 99, 99
+        ];
+
+        static PK_STR: &str =
+            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166";
+
+        let kp =
+            Keypair::from_seckey_slice(crate::SECP256K1, &SK_BYTES).unwrap();
+        let (pk, _parity) = XOnlyPublicKey::from_keypair(&kp);
+
+        assert_tokens(
+            &pk.compact(),
+            &[
+                Token::Tuple { len: 32 },
+                Token::U8(0x18),
+                Token::U8(0x84),
+                Token::U8(0x57),
+                Token::U8(0x81),
+                Token::U8(0xf6),
+                Token::U8(0x31),
+                Token::U8(0xc4),
+                Token::U8(0x8f),
+                Token::U8(0x1c),
+                Token::U8(0x97),
+                Token::U8(0x09),
+                Token::U8(0xe2),
+                Token::U8(0x30),
+                Token::U8(0x92),
+                Token::U8(0x06),
+                Token::U8(0x7d),
+                Token::U8(0x06),
+                Token::U8(0x83),
+                Token::U8(0x7f),
+                Token::U8(0x30),
+                Token::U8(0xaa),
+                Token::U8(0x0c),
+                Token::U8(0xd0),
+                Token::U8(0x54),
+                Token::U8(0x4a),
+                Token::U8(0xc8),
+                Token::U8(0x87),
+                Token::U8(0xfe),
+                Token::U8(0x91),
+                Token::U8(0xdd),
+                Token::U8(0xd1),
+                Token::U8(0x66),
+                Token::TupleEnd,
+            ],
+        );
+
+        assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
+        assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
+        assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn test_keypair_from_str() {
+        let ctx = crate::Secp256k1::new();
+        let keypair = Keypair::new(&ctx, &mut rand::thread_rng());
+        let mut buf = [0_u8; constants::SECRET_KEY_SIZE * 2]; // for hex digits
+        let s = to_hex(&keypair.secret_key().secret_bytes(), &mut buf).unwrap();
+        let parsed_key = Keypair::from_str(s).unwrap();
+        assert_eq!(parsed_key, keypair);
+    }
+
+    #[test]
+    #[cfg(all(
+        any(feature = "alloc", feature = "global-context"),
+        feature = "serde"
+    ))]
+    fn test_keypair_deserialize_serde() {
+        let ctx = crate::Secp256k1::new();
+        let sec_key_str =
+            "4242424242424242424242424242424242424242424242424242424242424242";
+        let keypair = Keypair::from_seckey_str(&ctx, sec_key_str).unwrap();
+
+        serde_test::assert_tokens(
+            &keypair.readable(),
+            &[Token::String(sec_key_str)],
+        );
+
+        let sec_key_bytes = keypair.secret_key().secret_bytes();
+        let tokens = std::iter::once(Token::Tuple { len: 32 })
+            .chain(sec_key_bytes.iter().copied().map(Token::U8))
+            .chain(std::iter::once(Token::TupleEnd))
+            .collect::<Vec<_>>();
+        serde_test::assert_tokens(&keypair.compact(), &tokens);
+    }
+}
+
+#[cfg(bench)]
+mod benches {
+    use std::collections::BTreeSet;
+
+    use test::Bencher;
+
+    use crate::constants::GENERATOR_X;
+    use crate::PublicKey;
+
+    #[bench]
+    fn bench_pk_ordering(b: &mut Bencher) {
+        let mut map = BTreeSet::new();
+        let mut g_slice = [02u8; 33];
+        g_slice[1..].copy_from_slice(&GENERATOR_X);
+        let g = PublicKey::from_slice(&g_slice).unwrap();
+        let mut pk = g;
+        b.iter(|| {
+            map.insert(pk);
+            pk = pk.combine(&pk).unwrap();
+        })
+    }
+}
diff --git a/modules/ecash-secp256k1/src/lib.rs b/modules/ecash-secp256k1/src/lib.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/lib.rs
@@ -0,0 +1,1218 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Rust bindings for Pieter Wuille's secp256k1 library, which is used for
+//! fast and accurate manipulation of ECDSA and Schnorr signatures on the
+//! secp256k1 curve. Such signatures are used extensively by the Bitcoin network
+//! and its derivatives.
+//!
+//! To minimize dependencies, some functions are feature-gated. To generate
+//! random keys or to re-randomize a context object, compile with the
+//! `rand` and `std` features. If you are willing to use these features, we
+//! have enabled an additional defense-in-depth sidechannel protection for
+//! our context objects, which re-blinds certain operations on secret key
+//! data. To de/serialize objects with serde, compile with "serde".
+//! **Important**: `serde` encoding is **not** the same as consensus
+//! encoding!
+//!
+//! Where possible, the bindings use the Rust type system to ensure that
+//! API usage errors are impossible. For example, the library uses context
+//! objects that contain precomputation tables which are created on object
+//! construction. Since this is a slow operation (10+ milliseconds, vs ~50
+//! microseconds for typical crypto operations, on a 2.70 Ghz i7-6820HQ)
+//! the tables are optional, giving a performance boost for users who only
+//! care about signing, only care about verification, or only care about
+//! parsing. In the upstream library, if you attempt to sign a message using
+//! a context that does not support this, it will trigger an assertion
+//! failure and terminate the program. In `rust-secp256k1`, this is caught
+//! at compile-time; in fact, it is impossible to compile code that will
+//! trigger any assertion failures in the upstream library.
+//!
+//! ```rust
+//! # #[cfg(all(feature = "rand", feature = "hashes", feature = "std"))] {
+//! use ecash_secp256k1::rand::rngs::OsRng;
+//! use ecash_secp256k1::{Secp256k1, Message};
+//! use ecash_secp256k1::hashes::{sha256, Hash};
+//!
+//! let secp = Secp256k1::new();
+//! let (secret_key, public_key) = secp.generate_keypair(&mut OsRng);
+//! let digest = sha256::Hash::hash("Hello World!".as_bytes());
+//! let message = Message::from_digest(digest.to_byte_array());
+//!
+//! let sig = secp.sign_ecdsa(&message, &secret_key);
+//! assert!(secp.verify_ecdsa(&message, &sig, &public_key).is_ok());
+//! # }
+//! ```
+//!
+//! If the "global-context" feature is enabled you have access to an alternate
+//! API.
+//!
+//! ```rust
+//! # #[cfg(all(feature = "global-context",
+//! #           feature = "hashes",
+//! #           feature = "rand",
+//! #           feature = "std"))] {
+//! use ecash_secp256k1::hashes::{sha256, Hash};
+//! use ecash_secp256k1::{generate_keypair, Message};
+//!
+//! let (secret_key, public_key) = generate_keypair(&mut rand::thread_rng());
+//! let digest = sha256::Hash::hash("Hello World!".as_bytes());
+//! let message = Message::from_digest(digest.to_byte_array());
+//!
+//! let sig = secret_key.sign_ecdsa(message);
+//! assert!(sig.verify(&message, &public_key).is_ok());
+//! # }
+//! ```
+//!
+//! The above code requires `rust-secp256k1` to be compiled with the `rand`,
+//! `hashes`, and `std` feature enabled, to get access to
+//! [`generate_keypair`](struct.Secp256k1.html#method.generate_keypair)
+//! Alternately, keys and messages can be parsed from slices, like
+//!
+//! ```rust
+//! # #[cfg(feature = "alloc")] {
+//! use ecash_secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
+//! # fn compute_hash(_: &[u8]) -> [u8; 32] { [0xab; 32] }
+//!
+//! let secp = Secp256k1::new();
+//! let secret_key = SecretKey::from_slice(&[0xcd; 32])
+//!     .expect("32 bytes, within curve order");
+//! let public_key = PublicKey::from_secret_key(&secp, &secret_key);
+//! // If the supplied byte slice was *not* the output of a cryptographic hash
+//! // function this would be cryptographically broken. It has been trivially
+//! // used in the past to execute attacks.
+//! let message = Message::from_digest(compute_hash(b"CSW is not Satoshi"));
+//!
+//! let sig = secp.sign_ecdsa(&message, &secret_key);
+//! assert!(secp.verify_ecdsa(&message, &sig, &public_key).is_ok());
+//! # }
+//! ```
+//!
+//! Users who only want to verify signatures can use a cheaper context, like so:
+//!
+//! ```rust
+//! # #[cfg(feature = "alloc")] {
+//! use ecash_secp256k1::{ecdsa, Message, PublicKey, Secp256k1};
+//!
+//! let secp = Secp256k1::verification_only();
+//!
+//! let public_key = PublicKey::from_slice(&[
+//!     0x02, 0xc6, 0x6e, 0x7d, 0x89, 0x66, 0xb5, 0xc5, 0x55, 0xaf, 0x58, 0x05,
+//!     0x98, 0x9d, 0xa9, 0xfb, 0xf8, 0xdb, 0x95, 0xe1, 0x56, 0x31, 0xce, 0x35,
+//!     0x8c, 0x3a, 0x17, 0x10, 0xc9, 0x62, 0x67, 0x90, 0x63,
+//! ])
+//! .expect(
+//!     "public keys must be 33 or 65 bytes, serialized according to SEC 2",
+//! );
+//!
+//! let message = Message::from_digest([
+//!     0xaa, 0xdf, 0x7d, 0xe7, 0x82, 0x03, 0x4f, 0xbe, 0x3d, 0x3d, 0xb2, 0xcb,
+//!     0x13, 0xc0, 0xcd, 0x91, 0xbf, 0x41, 0xcb, 0x08, 0xfa, 0xc7, 0xbd, 0x61,
+//!     0xd5, 0x44, 0x53, 0xcf, 0x6e, 0x82, 0xb4, 0x50,
+//! ]);
+//!
+//! let sig = ecdsa::Signature::from_compact(&[
+//!     0xdc, 0x4d, 0xc2, 0x64, 0xa9, 0xfe, 0xf1, 0x7a, 0x3f, 0x25, 0x34, 0x49,
+//!     0xcf, 0x8c, 0x39, 0x7a, 0xb6, 0xf1, 0x6f, 0xb3, 0xd6, 0x3d, 0x86, 0x94,
+//!     0x0b, 0x55, 0x86, 0x82, 0x3d, 0xfd, 0x02, 0xae, 0x3b, 0x46, 0x1b, 0xb4,
+//!     0x33, 0x6b, 0x5e, 0xcb, 0xae, 0xfd, 0x66, 0x27, 0xaa, 0x92, 0x2e, 0xfc,
+//!     0x04, 0x8f, 0xec, 0x0c, 0x88, 0x1c, 0x10, 0xc4, 0xc9, 0x42, 0x8f, 0xca,
+//!     0x69, 0xc1, 0x32, 0xa2,
+//! ])
+//! .expect("compact signatures are 64 bytes; DER signatures are 68-72 bytes");
+//!
+//! # #[cfg(not(secp256k1_fuzz))]
+//! assert!(secp.verify_ecdsa(&message, &sig, &public_key).is_ok());
+//! # }
+//! ```
+//!
+//! Observe that the same code using, say
+//! [`signing_only`](struct.Secp256k1.html#method.signing_only) to generate a
+//! context would simply not compile.
+//!
+//! ## Crate features/optional dependencies
+//!
+//! This crate provides the following opt-in Cargo features:
+//!
+//! * `std` - use standard Rust library, enabled by default.
+//! * `alloc` - use the `alloc` standard Rust library to provide heap
+//!   allocations.
+//! * `rand` - use `rand` library to provide random generator (e.g. to generate
+//!   keys).
+//! * `hashes` - use the `hashes` library.
+//! * `recovery` - enable functions that can compute the public key from
+//!   signature.
+//! * `lowmemory` - optimize the library for low-memory environments.
+//! * `global-context` - enable use of global secp256k1 context (implies `std`).
+//! * `serde` - implements serialization and deserialization for types in this
+//!   crate using `serde`. **Important**: `serde` encoding is **not** the same
+//!   as consensus encoding!
+
+// Coding conventions
+#![deny(non_upper_case_globals, non_camel_case_types, non_snake_case)]
+#![warn(
+    missing_docs,
+    missing_copy_implementations,
+    missing_debug_implementations
+)]
+#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
+// Experimental features we need.
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![cfg_attr(bench, feature(test))]
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+#[cfg(any(test, feature = "std"))]
+extern crate core;
+#[cfg(bench)]
+extern crate test;
+
+#[cfg(feature = "hashes")]
+pub extern crate hashes;
+
+#[macro_use]
+mod macros;
+#[macro_use]
+mod secret;
+mod context;
+mod key;
+
+pub mod constants;
+pub mod ecdh;
+pub mod ecdsa;
+pub mod scalar;
+pub mod schnorr;
+pub mod schnorrabc;
+#[cfg(feature = "serde")]
+mod serde_util;
+
+use core::marker::PhantomData;
+use core::ptr::NonNull;
+use core::{fmt, mem, str};
+
+#[cfg(all(feature = "global-context", feature = "std"))]
+pub use context::global::{self, SECP256K1};
+#[cfg(feature = "rand")]
+pub use rand;
+pub use secp256k1_sys as ffi;
+#[cfg(feature = "serde")]
+pub use serde;
+
+#[cfg(feature = "alloc")]
+pub use crate::context::{All, SignOnly, VerifyOnly};
+pub use crate::context::{
+    AllPreallocated, Context, PreallocatedContext, SignOnlyPreallocated,
+    Signing, Verification, VerifyOnlyPreallocated,
+};
+use crate::ffi::types::AlignedType;
+use crate::ffi::CPtr;
+pub use crate::key::{
+    InvalidParityValue, Keypair, Parity, PublicKey, SecretKey, XOnlyPublicKey,
+};
+pub use crate::scalar::Scalar;
+
+/// Trait describing something that promises to be a 32-byte uniformly random
+/// number.
+///
+/// In particular, anything implementing this trait must have neglibile
+/// probability of being zero, overflowing the group order, or equalling any
+/// specific value.
+///
+/// Since version 0.29 this has been deprecated; users should instead implement
+/// `Into<Message>` for types that satisfy these properties.
+#[deprecated(
+    since = "0.29.0",
+    note = "Please see v0.29.0 rust-secp256k1/CHANGELOG.md for suggestion"
+)]
+pub trait ThirtyTwoByteHash {
+    /// Converts the object into a 32-byte array
+    fn into_32(self) -> [u8; 32];
+}
+
+/// A (hashed) message input to an ECDSA signature.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Message([u8; constants::MESSAGE_SIZE]);
+impl_array_newtype!(Message, u8, constants::MESSAGE_SIZE);
+impl_pretty_debug!(Message);
+
+impl Message {
+    /// Creates a [`Message`] from a 32 byte slice `digest`.
+    ///
+    /// Converts a `MESSAGE_SIZE`-byte slice to a message object. **WARNING:**
+    /// the slice has to be a cryptographically secure hash of the actual
+    /// message that's going to be signed. Otherwise the result of signing
+    /// isn't a secure signature, see
+    /// <https://twitter.com/pwuille/status/1063582706288586752>.
+    #[inline]
+    #[deprecated(since = "0.28.0", note = "use from_digest instead")]
+    pub fn from_slice(digest: &[u8]) -> Result<Message, Error> {
+        #[allow(deprecated)]
+        Message::from_digest_slice(digest)
+    }
+
+    /// Creates a [`Message`] from a `digest`.
+    ///
+    /// The `digest` array has to be a cryptographically secure hash of the
+    /// actual message that's going to be signed. Otherwise the result of
+    /// signing isn't a [secure signature].
+    ///
+    /// [secure signature]:
+    /// <https://twitter.com/pwuille/status/1063582706288586752>
+    #[inline]
+    pub fn from_digest(digest: [u8; 32]) -> Message {
+        Message(digest)
+    }
+
+    /// Creates a [`Message`] from a 32 byte slice `digest`.
+    ///
+    /// The slice has to be 32 bytes long and be a cryptographically secure hash
+    /// of the actual message that's going to be signed. Otherwise the
+    /// result of signing isn't a [secure signature].
+    ///
+    /// This method is deprecated. It's best to use [`Message::from_digest`]
+    /// directly with an array. If your hash engine doesn't return an array
+    /// for some reason use `.try_into()` on its output.
+    ///
+    /// # Errors
+    ///
+    /// If `digest` is not exactly 32 bytes long.
+    ///
+    /// [secure signature]:
+    /// <https://twitter.com/pwuille/status/1063582706288586752>
+    #[inline]
+    #[deprecated(since = "TBD", note = "use from_digest instead")]
+    pub fn from_digest_slice(digest: &[u8]) -> Result<Message, Error> {
+        Ok(Message::from_digest(
+            digest.try_into().map_err(|_| Error::InvalidMessage)?,
+        ))
+    }
+}
+
+#[allow(deprecated)]
+impl<T: ThirtyTwoByteHash> From<T> for Message {
+    /// Converts a 32-byte hash directly to a message without error paths.
+    fn from(t: T) -> Message {
+        Message(t.into_32())
+    }
+}
+
+impl fmt::LowerHex for Message {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        for byte in self.0.iter() {
+            write!(f, "{:02x}", byte)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for Message {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
+/// The main error type for this library.
+#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
+pub enum Error {
+    /// Signature failed verification.
+    IncorrectSignature,
+    /// Bad sized message ("messages" are actually fixed-sized digests
+    /// [`constants::MESSAGE_SIZE`]).
+    InvalidMessage,
+    /// Bad public key.
+    InvalidPublicKey,
+    /// Bad signature.
+    InvalidSignature,
+    /// Bad secret key.
+    InvalidSecretKey,
+    /// Bad shared secret.
+    InvalidSharedSecret,
+    /// Bad recovery id.
+    InvalidRecoveryId,
+    /// Tried to add/multiply by an invalid tweak.
+    InvalidTweak,
+    /// Didn't pass enough memory to context creation with preallocated memory.
+    NotEnoughMemory,
+    /// Bad set of public keys.
+    InvalidPublicKeySum,
+    /// The only valid parity values are 0 or 1.
+    InvalidParityValue(key::InvalidParityValue),
+    /// Bad EllSwift value
+    InvalidEllSwift,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+        use Error::*;
+
+        match *self {
+            IncorrectSignature => f.write_str("signature failed verification"),
+            InvalidMessage => {
+                f.write_str("message was not 32 bytes (do you need to hash?)")
+            }
+            InvalidPublicKey => f.write_str("malformed public key"),
+            InvalidSignature => f.write_str("malformed signature"),
+            InvalidSecretKey => {
+                f.write_str("malformed or out-of-range secret key")
+            }
+            InvalidSharedSecret => {
+                f.write_str("malformed or out-of-range shared secret")
+            }
+            InvalidRecoveryId => f.write_str("bad recovery id"),
+            InvalidTweak => f.write_str("bad tweak"),
+            NotEnoughMemory => f.write_str("not enough memory allocated"),
+            InvalidPublicKeySum => f.write_str(
+                "the sum of public keys was invalid or the input vector \
+                 lengths was less than 1",
+            ),
+            InvalidParityValue(e) => write_err!(f, "couldn't create parity"; e),
+            InvalidEllSwift => f.write_str("malformed EllSwift value"),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        match self {
+            Error::IncorrectSignature => None,
+            Error::InvalidMessage => None,
+            Error::InvalidPublicKey => None,
+            Error::InvalidSignature => None,
+            Error::InvalidSecretKey => None,
+            Error::InvalidSharedSecret => None,
+            Error::InvalidRecoveryId => None,
+            Error::InvalidTweak => None,
+            Error::NotEnoughMemory => None,
+            Error::InvalidPublicKeySum => None,
+            Error::InvalidParityValue(error) => Some(error),
+            Error::InvalidEllSwift => None,
+        }
+    }
+}
+
+/// The secp256k1 engine, used to execute all signature operations.
+pub struct Secp256k1<C: Context> {
+    ctx: NonNull<ffi::Context>,
+    phantom: PhantomData<C>,
+}
+
+// The underlying secp context does not contain any references to memory it does
+// not own.
+unsafe impl<C: Context> Send for Secp256k1<C> {}
+// The API does not permit any mutation of `Secp256k1` objects except through
+// `&mut` references.
+unsafe impl<C: Context> Sync for Secp256k1<C> {}
+
+impl<C: Context> PartialEq for Secp256k1<C> {
+    fn eq(&self, _other: &Secp256k1<C>) -> bool {
+        true
+    }
+}
+
+impl<C: Context> Eq for Secp256k1<C> {}
+
+impl<C: Context> Drop for Secp256k1<C> {
+    fn drop(&mut self) {
+        unsafe {
+            let size = ffi::secp256k1_context_preallocated_clone_size(
+                self.ctx.as_ptr(),
+            );
+            ffi::secp256k1_context_preallocated_destroy(self.ctx);
+
+            C::deallocate(self.ctx.as_ptr() as _, size);
+        }
+    }
+}
+
+impl<C: Context> fmt::Debug for Secp256k1<C> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "<secp256k1 context {:?}, {}>", self.ctx, C::DESCRIPTION)
+    }
+}
+
+impl<C: Context> Secp256k1<C> {
+    /// Getter for the raw pointer to the underlying secp256k1 context. This
+    /// shouldn't be needed with normal usage of the library. It enables
+    /// extending the Secp256k1 with more cryptographic algorithms outside of
+    /// this crate.
+    pub fn ctx(&self) -> NonNull<ffi::Context> {
+        self.ctx
+    }
+
+    /// Returns the required memory for a preallocated context buffer in a
+    /// generic manner(sign/verify/all).
+    pub fn preallocate_size_gen() -> usize {
+        let word_size = mem::size_of::<AlignedType>();
+        let bytes =
+            unsafe { ffi::secp256k1_context_preallocated_size(C::FLAGS) };
+
+        (bytes + word_size - 1) / word_size
+    }
+
+    /// (Re)randomizes the Secp256k1 context for extra sidechannel resistance.
+    ///
+    /// Requires compilation with "rand" feature. See comment by Gregory Maxwell
+    /// in [libsecp256k1](https://github.com/bitcoin-core/secp256k1) commit
+    /// d2275795ff22a6f4738869f5528fbbb61738aa48.
+    #[cfg(feature = "rand")]
+    pub fn randomize<R: rand::Rng + ?Sized>(&mut self, rng: &mut R) {
+        let mut seed = [0u8; 32];
+        rng.fill_bytes(&mut seed);
+        self.seeded_randomize(&seed);
+    }
+
+    /// (Re)randomizes the Secp256k1 context for extra sidechannel resistance
+    /// given 32 bytes of cryptographically-secure random data;
+    /// see comment in libsecp256k1 commit d2275795f by Gregory Maxwell.
+    pub fn seeded_randomize(&mut self, seed: &[u8; 32]) {
+        unsafe {
+            let err =
+                ffi::secp256k1_context_randomize(self.ctx, seed.as_c_ptr());
+            // This function cannot fail; it has an error return for
+            // future-proofing. We do not expose this error since it
+            // is impossible to hit, and we have precedent for not
+            // exposing impossible errors (for example in
+            // `PublicKey::from_secret_key` where it is impossible to create an
+            // invalid secret key through the API.)
+            // However, if this DOES fail, the result is potentially weaker
+            // side-channel resistance, which is deadly and
+            // undetectable, so we take out the entire thread to be
+            // on the safe side.
+            assert_eq!(err, 1);
+        }
+    }
+}
+
+impl<C: Signing> Secp256k1<C> {
+    /// Generates a random keypair. Convenience function for [`SecretKey::new`]
+    /// and [`PublicKey::from_secret_key`].
+    #[inline]
+    #[cfg(feature = "rand")]
+    pub fn generate_keypair<R: rand::Rng + ?Sized>(
+        &self,
+        rng: &mut R,
+    ) -> (key::SecretKey, key::PublicKey) {
+        let sk = key::SecretKey::new(rng);
+        let pk = key::PublicKey::from_secret_key(self, &sk);
+        (sk, pk)
+    }
+}
+
+/// Generates a random keypair using the global [`SECP256K1`] context.
+#[inline]
+#[cfg(all(feature = "global-context", feature = "rand"))]
+pub fn generate_keypair<R: rand::Rng + ?Sized>(
+    rng: &mut R,
+) -> (key::SecretKey, key::PublicKey) {
+    SECP256K1.generate_keypair(rng)
+}
+
+/// Utility function used to parse hex into a target u8 buffer. Returns
+/// the number of bytes converted or an error if it encounters an invalid
+/// character or unexpected end of string.
+fn from_hex(hex: &str, target: &mut [u8]) -> Result<usize, ()> {
+    if hex.len() % 2 == 1 || hex.len() > target.len() * 2 {
+        return Err(());
+    }
+
+    let mut b = 0;
+    let mut idx = 0;
+    for c in hex.bytes() {
+        b <<= 4;
+        match c {
+            b'A'..=b'F' => b |= c - b'A' + 10,
+            b'a'..=b'f' => b |= c - b'a' + 10,
+            b'0'..=b'9' => b |= c - b'0',
+            _ => return Err(()),
+        }
+        if (idx & 1) == 1 {
+            target[idx / 2] = b;
+            b = 0;
+        }
+        idx += 1;
+    }
+    Ok(idx / 2)
+}
+
+/// Utility function used to encode hex into a target u8 buffer. Returns
+/// a reference to the target buffer as an str. Returns an error if the target
+/// buffer isn't big enough.
+#[inline]
+fn to_hex<'a>(src: &[u8], target: &'a mut [u8]) -> Result<&'a str, ()> {
+    let hex_len = src.len() * 2;
+    if target.len() < hex_len {
+        return Err(());
+    }
+    const HEX_TABLE: [u8; 16] = *b"0123456789abcdef";
+
+    let mut i = 0;
+    for &b in src {
+        target[i] = HEX_TABLE[usize::from(b >> 4)];
+        target[i + 1] = HEX_TABLE[usize::from(b & 0b00001111)];
+        i += 2;
+    }
+    let result = &target[..hex_len];
+    debug_assert!(str::from_utf8(result).is_ok());
+    return unsafe { Ok(str::from_utf8_unchecked(result)) };
+}
+
+#[cfg(feature = "rand")]
+pub(crate) fn random_32_bytes<R: rand::Rng + ?Sized>(rng: &mut R) -> [u8; 32] {
+    let mut ret = [0u8; 32];
+    rng.fill(&mut ret);
+    ret
+}
+
+#[cfg(test)]
+mod tests {
+    use std::str::FromStr;
+
+    use hex_lit::hex;
+    #[cfg(target_arch = "wasm32")]
+    use wasm_bindgen_test::wasm_bindgen_test as test;
+
+    use super::*;
+
+    #[ignore]
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    // In rustc 1.72 this Clippy lint was pulled out of clippy and into rustc,
+    // and was made deny-by-default, breaking compilation of this test.
+    // Aside from this breaking change, which there is no point in bugging,
+    // the rename was done so clumsily that you need four separate "allow"s
+    // to disable this wrong lint.
+    #[allow(unknown_lints)]
+    #[allow(renamed_and_removed_lints)]
+    #[allow(undropped_manually_drops)]
+    #[allow(clippy::unknown_manually_drops)]
+    fn test_raw_ctx() {
+        use std::mem::{forget, ManuallyDrop};
+
+        let ctx_full = Secp256k1::new();
+        let ctx_sign = Secp256k1::signing_only();
+        let ctx_vrfy = Secp256k1::verification_only();
+
+        let full = unsafe { Secp256k1::from_raw_all(ctx_full.ctx) };
+        let sign = unsafe { Secp256k1::from_raw_signing_only(ctx_sign.ctx) };
+        let mut vrfy =
+            unsafe { Secp256k1::from_raw_verification_only(ctx_vrfy.ctx) };
+
+        let (sk, pk) = full.generate_keypair(&mut rand::thread_rng());
+        let msg = Message::from_digest([2u8; 32]);
+        // Try signing
+        assert_eq!(sign.sign_ecdsa(&msg, &sk), full.sign_ecdsa(&msg, &sk));
+        let sig = full.sign_ecdsa(&msg, &sk);
+
+        // Try verifying
+        assert!(vrfy.verify_ecdsa(&msg, &sig, &pk).is_ok());
+        assert!(full.verify_ecdsa(&msg, &sig, &pk).is_ok());
+
+        // The following drop will have no effect; in fact, they will trigger a
+        // compiler error because manually dropping a `ManuallyDrop` is
+        // almost certainly incorrect. If you want to drop the inner
+        // object you should called `ManuallyDrop::drop`.
+        drop(full);
+        // This will actually drop the context, though it will leave `full`
+        // accessible and in an invalid state. However, this is almost
+        // certainly what you want to do.
+        drop(ctx_full);
+        unsafe {
+            // Need to compute the allocation size, and need to do so *before*
+            // dropping anything.
+            let sz = ffi::secp256k1_context_preallocated_clone_size(
+                ctx_sign.ctx.as_ptr(),
+            );
+            // We can alternately drop the `ManuallyDrop` by unwrapping it and
+            // then letting it be dropped. This is actually a safe
+            // function, but it will destruct the underlying context
+            // without deallocating it...
+            ManuallyDrop::into_inner(sign);
+            // ...leaving us holding the bag to deallocate the context's memory
+            // without double-calling `secp256k1_context_destroy`,
+            // which cannot be done safely.
+            SignOnly::deallocate(ctx_sign.ctx.as_ptr() as *mut u8, sz);
+            forget(ctx_sign);
+        }
+
+        unsafe {
+            // Finally, we can call `ManuallyDrop::drop`, which has the same
+            // effect, but
+            let sz = ffi::secp256k1_context_preallocated_clone_size(
+                ctx_vrfy.ctx.as_ptr(),
+            );
+            // leaves the `ManuallyDrop` itself accessible. This is marked
+            // unsafe.
+            ManuallyDrop::drop(&mut vrfy);
+            VerifyOnly::deallocate(ctx_vrfy.ctx.as_ptr() as *mut u8, sz);
+            forget(ctx_vrfy);
+        }
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    #[test]
+    #[ignore]
+    // Panicking from C may trap (SIGILL) intentionally, so we test this
+    // manually.
+    #[cfg(feature = "alloc")]
+    fn test_panic_raw_ctx_should_terminate_abnormally() {
+        // Trying to use an all-zeros public key should cause an ARG_CHECK to
+        // trigger.
+        let pk = PublicKey::from(unsafe { ffi::PublicKey::new() });
+        pk.serialize();
+    }
+
+    #[test]
+    #[ignore]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn test_preallocation() {
+        use crate::ffi::types::AlignedType;
+
+        let mut buf_ful =
+            vec![AlignedType::zeroed(); Secp256k1::preallocate_size()];
+        let mut buf_sign =
+            vec![AlignedType::zeroed(); Secp256k1::preallocate_signing_size()];
+        let mut buf_vfy = vec![
+            AlignedType::zeroed();
+            Secp256k1::preallocate_verification_size()
+        ];
+
+        let full = Secp256k1::preallocated_new(&mut buf_ful).unwrap();
+        let sign = Secp256k1::preallocated_signing_only(&mut buf_sign).unwrap();
+        // Use after free?
+        let vrfy =
+            Secp256k1::preallocated_verification_only(&mut buf_vfy).unwrap();
+
+        let (sk, pk) = full.generate_keypair(&mut rand::thread_rng());
+        let msg = Message::from_digest([2u8; 32]);
+        // Try signing
+        assert_eq!(sign.sign_ecdsa(&msg, &sk), full.sign_ecdsa(&msg, &sk));
+        let sig = full.sign_ecdsa(&msg, &sk);
+
+        // Try verifying
+        assert!(vrfy.verify_ecdsa(&msg, &sig, &pk).is_ok());
+        assert!(full.verify_ecdsa(&msg, &sig, &pk).is_ok());
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn capabilities() {
+        let sign = Secp256k1::signing_only();
+        let vrfy = Secp256k1::verification_only();
+        let full = Secp256k1::new();
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest(msg);
+
+        // Try key generation
+        let (sk, pk) = full.generate_keypair(&mut rand::thread_rng());
+
+        // Try signing
+        assert_eq!(sign.sign_ecdsa(&msg, &sk), full.sign_ecdsa(&msg, &sk));
+        let sig = full.sign_ecdsa(&msg, &sk);
+
+        // Try verifying
+        assert!(vrfy.verify_ecdsa(&msg, &sig, &pk).is_ok());
+        assert!(full.verify_ecdsa(&msg, &sig, &pk).is_ok());
+
+        // Check that we can produce keys from slices with no precomputation
+        let (pk_slice, sk_slice) = (&pk.serialize(), &sk[..]);
+        let new_pk = PublicKey::from_slice(pk_slice).unwrap();
+        let new_sk = SecretKey::from_slice(sk_slice).unwrap();
+        assert_eq!(sk, new_sk);
+        assert_eq!(pk, new_pk);
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn signature_serialize_roundtrip() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        for _ in 0..100 {
+            let msg = crate::random_32_bytes(&mut rand::thread_rng());
+            let msg = Message::from_digest(msg);
+
+            let (sk, _) = s.generate_keypair(&mut rand::thread_rng());
+            let sig1 = s.sign_ecdsa(&msg, &sk);
+            let der = sig1.serialize_der();
+            let sig2 = ecdsa::Signature::from_der(&der[..]).unwrap();
+            assert_eq!(sig1, sig2);
+
+            let compact = sig1.serialize_compact();
+            let sig2 = ecdsa::Signature::from_compact(&compact[..]).unwrap();
+            assert_eq!(sig1, sig2);
+
+            assert!(ecdsa::Signature::from_compact(&der[..]).is_err());
+            assert!(ecdsa::Signature::from_compact(&compact[0..4]).is_err());
+            assert!(ecdsa::Signature::from_der(&compact[..]).is_err());
+            assert!(ecdsa::Signature::from_der(&der[0..4]).is_err());
+        }
+    }
+
+    #[test]
+    fn signature_display() {
+        const HEX_STR: &str =
+            "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba3\
+            2d751c0f7acb21ac8a0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83\
+            ee297c7a5587b2011c4fcc72eab45";
+        let byte_str = hex!(HEX_STR);
+
+        assert_eq!(
+            ecdsa::Signature::from_der(&byte_str).expect("byte str decode"),
+            ecdsa::Signature::from_str(HEX_STR).expect("byte str decode")
+        );
+
+        let sig = ecdsa::Signature::from_str(HEX_STR).expect("byte str decode");
+        assert_eq!(&sig.to_string(), HEX_STR);
+        assert_eq!(&format!("{:?}", sig), HEX_STR);
+
+        assert!(ecdsa::Signature::from_str(
+            "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a\
+             0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011\
+             c4fcc72eab4"
+        )
+        .is_err());
+        assert!(ecdsa::Signature::from_str(
+            "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a\
+             0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011\
+             c4fcc72eab"
+        )
+        .is_err());
+        assert!(ecdsa::Signature::from_str(
+            "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a\
+             0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011\
+             c4fcc72eabxx"
+        )
+        .is_err());
+        assert!(ecdsa::Signature::from_str(
+            "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a\
+             0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011\
+             c4fcc72eab4572022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a558\
+             7b2011c4fcc72eab4572022100e89bd46bb3a5a62adc679f659b7ce876d83ee297\
+             c7a5587b2011c4fcc72eab4572022100e89bd46bb3a5a62adc679f659b7ce876d8\
+             3ee297c7a5587b2011c4fcc72eab4572022100e89bd46bb3a5a62adc679f659b7c\
+             e876d83ee297c7a5587b2011c4fcc72eab45"
+        )
+        .is_err());
+
+        // 71 byte signature
+        let hex_str =
+            "30450221009d0bad576719d32ae76bedb34c774866673cbde3f4e12951555c9408\
+             e6ce774b02202876e7102f204f6bfee26c967c3926ce702cf97d4b010062e193f7\
+             63190f6776";
+        let sig = ecdsa::Signature::from_str(hex_str).expect("byte str decode");
+        assert_eq!(&format!("{}", sig), hex_str);
+    }
+
+    #[test]
+    fn signature_lax_der() {
+        macro_rules! check_lax_sig(
+            ($hex:expr) => ({
+                let sig = hex!($hex);
+                assert!(ecdsa::Signature::from_der_lax(&sig[..]).is_ok());
+            })
+        );
+
+        check_lax_sig!(
+            "304402204c2dd8a9b6f8d425fcd8ee9a20ac73b619906a6367eac6cb93e7037522\
+             5ec0160220356878eff111ff3663d7e6bf08947f94443845e0dcc54961664d922f\
+             7660b80c"
+        );
+        check_lax_sig!(
+            "304402202ea9d51c7173b1d96d331bd41b3d1b4e78e66148e64ed5992abd6ca662\
+             90321c0220628c47517e049b3e41509e9d71e480a0cdc766f8cdec265ef0017711\
+             c1b5336f"
+        );
+        check_lax_sig!(
+            "3045022100bf8e050c85ffa1c313108ad8c482c4849027937916374617af3f2e9a\
+             881861c9022023f65814222cab09d5ec41032ce9c72ca96a5676020736614de7b7\
+             8a4e55325a"
+        );
+        check_lax_sig!(
+            "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a\
+             0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011\
+             c4fcc72eab45"
+        );
+        check_lax_sig!(
+            "3046022100eaa5f90483eb20224616775891397d47efa64c68b969db1dacb1c30a\
+             cdfc50aa022100cf9903bbefb1c8000cf482b0aeeb5af19287af20bd794de11d82\
+             716f9bae3db1"
+        );
+        check_lax_sig!(
+            "3045022047d512bc85842ac463ca3b669b62666ab8672ee60725b6c06759e476ce\
+             bdc6c102210083805e93bd941770109bcc797784a71db9e48913f702c56e60b1c3\
+             e2ff379a60"
+        );
+        check_lax_sig!(
+            "3044022023ee4e95151b2fbbb08a72f35babe02830d14d54bd7ed1320e4751751d\
+             1baa4802206235245254f58fd1be6ff19ca291817da76da65c2f6d81d654b5185d\
+             d86b8acf"
+        );
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn sign_and_verify_ecdsa() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let noncedata = [42u8; 32];
+        for _ in 0..100 {
+            let msg = crate::random_32_bytes(&mut rand::thread_rng());
+            let msg = Message::from_digest(msg);
+
+            let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+            let sig = s.sign_ecdsa(&msg, &sk);
+            assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Ok(()));
+            let noncedata_sig =
+                s.sign_ecdsa_with_noncedata(&msg, &sk, &noncedata);
+            assert_eq!(s.verify_ecdsa(&msg, &noncedata_sig, &pk), Ok(()));
+            let low_r_sig = s.sign_ecdsa_low_r(&msg, &sk);
+            assert_eq!(s.verify_ecdsa(&msg, &low_r_sig, &pk), Ok(()));
+            let grind_r_sig = s.sign_ecdsa_grind_r(&msg, &sk, 1);
+            assert_eq!(s.verify_ecdsa(&msg, &grind_r_sig, &pk), Ok(()));
+            let compact = sig.serialize_compact();
+            if compact[0] < 0x80 {
+                assert_eq!(sig, low_r_sig);
+            } else {
+                // mocked sig generation doesn't produce low-R sigs
+                #[cfg(not(secp256k1_fuzz))]
+                assert_ne!(sig, low_r_sig);
+            }
+            // mocked sig generation doesn't produce low-R sigs
+            #[cfg(not(secp256k1_fuzz))]
+            assert!(ecdsa::compact_sig_has_zero_first_bit(&low_r_sig.0));
+            // mocked sig generation doesn't produce low-R sigs
+            #[cfg(not(secp256k1_fuzz))]
+            assert!(ecdsa::der_length_check(&grind_r_sig.0, 70));
+        }
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn sign_and_verify_extreme() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        // Wild keys: 1, CURVE_ORDER - 1
+        // Wild msgs: 1, CURVE_ORDER - 1
+        let mut wild_keys = [[0u8; 32]; 2];
+        let mut wild_msgs = [[0u8; 32]; 2];
+
+        wild_keys[0][0] = 1;
+        wild_msgs[0][0] = 1;
+
+        use constants;
+        wild_keys[1][..].copy_from_slice(&constants::CURVE_ORDER[..]);
+        wild_msgs[1][..].copy_from_slice(&constants::CURVE_ORDER[..]);
+
+        wild_keys[1][0] -= 1;
+        wild_msgs[1][0] -= 1;
+
+        for key in wild_keys
+            .iter()
+            .map(|k| SecretKey::from_slice(&k[..]).unwrap())
+        {
+            for msg in wild_msgs.into_iter().map(Message::from_digest) {
+                let sig = s.sign_ecdsa(&msg, &key);
+                let low_r_sig = s.sign_ecdsa_low_r(&msg, &key);
+                let grind_r_sig = s.sign_ecdsa_grind_r(&msg, &key, 1);
+                let pk = PublicKey::from_secret_key(&s, &key);
+                assert_eq!(s.verify_ecdsa(&msg, &sig, &pk), Ok(()));
+                assert_eq!(s.verify_ecdsa(&msg, &low_r_sig, &pk), Ok(()));
+                assert_eq!(s.verify_ecdsa(&msg, &grind_r_sig, &pk), Ok(()));
+            }
+        }
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn sign_and_verify_fail() {
+        let mut s = Secp256k1::new();
+        s.randomize(&mut rand::thread_rng());
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest(msg);
+
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+
+        let sig = s.sign_ecdsa(&msg, &sk);
+
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest(msg);
+        assert_eq!(
+            s.verify_ecdsa(&msg, &sig, &pk),
+            Err(Error::IncorrectSignature)
+        );
+    }
+
+    #[test]
+    #[allow(deprecated)]
+    fn test_bad_slice() {
+        assert_eq!(
+            ecdsa::Signature::from_der(&[0; constants::MAX_SIGNATURE_SIZE + 1]),
+            Err(Error::InvalidSignature)
+        );
+        assert_eq!(
+            ecdsa::Signature::from_der(&[0; constants::MAX_SIGNATURE_SIZE]),
+            Err(Error::InvalidSignature)
+        );
+
+        assert_eq!(
+            Message::from_digest_slice(&[0; constants::MESSAGE_SIZE - 1]),
+            Err(Error::InvalidMessage)
+        );
+        assert_eq!(
+            Message::from_digest_slice(&[0; constants::MESSAGE_SIZE + 1]),
+            Err(Error::InvalidMessage)
+        );
+        assert!(
+            Message::from_digest_slice(&[0; constants::MESSAGE_SIZE]).is_ok()
+        );
+        assert!(
+            Message::from_digest_slice(&[1; constants::MESSAGE_SIZE]).is_ok()
+        );
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn test_hex() {
+        use rand::RngCore;
+
+        use super::to_hex;
+
+        let mut rng = rand::thread_rng();
+        const AMOUNT: usize = 1024;
+        for i in 0..AMOUNT {
+            // 255 isn't a valid utf8 character.
+            let mut hex_buf = [255u8; AMOUNT * 2];
+            let mut src_buf = [0u8; AMOUNT];
+            let mut result_buf = [0u8; AMOUNT];
+            let src = &mut src_buf[0..i];
+            rng.fill_bytes(src);
+
+            let hex = to_hex(src, &mut hex_buf).unwrap();
+            assert_eq!(from_hex(hex, &mut result_buf).unwrap(), i);
+            assert_eq!(src, &result_buf[..i]);
+        }
+
+        assert!(to_hex(&[1; 2], &mut [0u8; 3]).is_err());
+        assert!(to_hex(&[1; 2], &mut [0u8; 4]).is_ok());
+        assert!(from_hex("deadbeaf", &mut [0u8; 3]).is_err());
+        assert!(from_hex("deadbeaf", &mut [0u8; 4]).is_ok());
+        assert!(from_hex("a", &mut [0u8; 4]).is_err());
+        assert!(from_hex("ag", &mut [0u8; 4]).is_err());
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))] // fuzz-sigs have fixed size/format
+    #[cfg(any(feature = "alloc", feature = "std"))]
+    fn test_noncedata() {
+        let secp = Secp256k1::new();
+        let msg = hex!(
+            "887d04bb1cf1b1554f1b268dfe62d13064ca67ae45348d50d1392ce2d13418ac"
+        );
+        let msg = Message::from_digest(msg);
+        let noncedata = [42u8; 32];
+        let sk = SecretKey::from_str(
+            "57f0148f94d13095cfda539d0da0d1541304b678d8b36e243980aab4e1b7cead",
+        )
+        .unwrap();
+        let expected_sig = hex!(
+            "daaf6acb56826c1b98263d7b387e9577cf3bf5eeecdff9da78cb519582b3fa2c3d\
+             7154450694e7e3bcbe83bdf3647c3644fc068f4fce822b54102b2eede4dd5c"
+        );
+        let expected_sig =
+            ecdsa::Signature::from_compact(&expected_sig).unwrap();
+
+        let sig = secp.sign_ecdsa_with_noncedata(&msg, &sk, &noncedata);
+
+        assert_eq!(expected_sig, sig);
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))] // fixed sig vectors can't work with fuzz-sigs
+    #[cfg(any(feature = "alloc", feature = "std"))]
+    fn test_low_s() {
+        // nb this is a transaction on testnet
+        // txid 8ccc87b72d766ab3128f03176bb1c98293f2d1f85ebfaf07b82cc81ea6891fa9
+        //      input number 3
+        let sig = hex!(
+            "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a\
+             0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011\
+             c4fcc72eab45"
+        );
+        let pk = hex!(
+            "031ee99d2b786ab3b0991325f2de8489246a6a3fdb700f6d0511b1d80cf5f4cd43"
+        );
+        let msg = hex!(
+            "a4965ca63b7d8562736ceec36dfa5a11bf426eb65be8ea3f7a49ae363032da0d"
+        );
+
+        let secp = Secp256k1::new();
+        let mut sig = ecdsa::Signature::from_der(&sig[..]).unwrap();
+        let pk = PublicKey::from_slice(&pk[..]).unwrap();
+        let msg = Message::from_digest(msg);
+
+        // without normalization we expect this will fail
+        assert_eq!(
+            secp.verify_ecdsa(&msg, &sig, &pk),
+            Err(Error::IncorrectSignature)
+        );
+        // after normalization it should pass
+        sig.normalize_s();
+        assert_eq!(secp.verify_ecdsa(&msg, &sig, &pk), Ok(()));
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))] // fuzz-sigs have fixed size/format
+    #[cfg(any(feature = "alloc", feature = "std"))]
+    fn test_low_r() {
+        let secp = Secp256k1::new();
+        let msg = hex!(
+            "887d04bb1cf1b1554f1b268dfe62d13064ca67ae45348d50d1392ce2d13418ac"
+        );
+        let msg = Message::from_digest(msg);
+        let sk = SecretKey::from_str(
+            "57f0148f94d13095cfda539d0da0d1541304b678d8b36e243980aab4e1b7cead",
+        )
+        .unwrap();
+        let expected_sig = hex!(
+            "02bdb3b9178d424b7f3b81f3c4e3f8feb699caac940eca8bb4ed8ce6abc9523244\
+             358e88e70b8e78a1ffe75288440a2ad84c3b79b56606faaea598f0d5ca6f75"
+        );
+        let expected_sig =
+            ecdsa::Signature::from_compact(&expected_sig).unwrap();
+
+        let sig = secp.sign_ecdsa_low_r(&msg, &sk);
+
+        assert_eq!(expected_sig, sig);
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))] // fuzz-sigs have fixed size/format
+    #[cfg(any(feature = "alloc", feature = "std"))]
+    fn test_grind_r() {
+        let secp = Secp256k1::new();
+        let msg = hex!(
+            "ef2d5b9a7c61865a95941d0f04285420560df7e9d76890ac1b8867b12ce43167"
+        );
+        let msg = Message::from_digest(msg);
+        let sk = SecretKey::from_str(
+            "848355d75fe1c354cf05539bb29b2015f1863065bcb6766b44d399ab95c3fa0b",
+        )
+        .unwrap();
+        let expected_sig = ecdsa::Signature::from_str(
+            "3043021f4193abeb40007545ccd4c5213a2b4d7ee16af7941ae9a8f47562679601\
+             f6a7022014059a75d9cf3fd0b278b796adc7c4560d5216cf40a9a018b84c535ebf\
+             2246d2"
+        )
+        .unwrap();
+
+        let sig = secp.sign_ecdsa_grind_r(&msg, &sk, 2);
+
+        assert_eq!(expected_sig, sig);
+    }
+
+    #[cfg(feature = "serde")]
+    #[cfg(not(secp256k1_fuzz))] // fixed sig vectors can't work with fuzz-sigs
+    #[cfg(any(feature = "alloc", feature = "std"))]
+    #[test]
+    fn test_serde() {
+        use serde_test::{assert_tokens, Configure, Token};
+
+        let s = Secp256k1::new();
+
+        let msg = Message::from_digest([1; 32]);
+        let sk = SecretKey::from_slice(&[2; 32]).unwrap();
+        let sig = s.sign_ecdsa(&msg, &sk);
+        static SIG_BYTES: [u8; 71] = [
+            48, 69, 2, 33, 0, 165, 112, 165, 56, 166, 96, 143, 117, 5, 155, 80,
+            102, 225, 52, 1, 13, 209, 246, 209, 110, 88, 168, 209, 53, 173, 32,
+            177, 155, 197, 72, 135, 167, 2, 32, 77, 17, 27, 229, 166, 73, 176,
+            244, 166, 254, 22, 163, 208, 183, 156, 83, 196, 176, 110, 99, 45,
+            172, 20, 195, 193, 218, 96, 179, 212, 63, 113, 198,
+        ];
+        static SIG_STR: &str = "\
+            3045022100a570a538a6608f75059b5066e134010dd1f6d16e58a8d135ad20b19b\
+            c54887a702204d111be5a649b0f4a6fe16a3d0b79c53c4b06e632dac14c3c1da60\
+            b3d43f71c6\
+        ";
+
+        assert_tokens(&sig.compact(), &[Token::BorrowedBytes(&SIG_BYTES[..])]);
+        assert_tokens(&sig.compact(), &[Token::Bytes(&SIG_BYTES)]);
+        assert_tokens(&sig.compact(), &[Token::ByteBuf(&SIG_BYTES)]);
+
+        assert_tokens(&sig.readable(), &[Token::BorrowedStr(SIG_STR)]);
+        assert_tokens(&sig.readable(), &[Token::Str(SIG_STR)]);
+        assert_tokens(&sig.readable(), &[Token::String(SIG_STR)]);
+    }
+
+    #[cfg(feature = "global-context")]
+    #[test]
+    fn test_global_context() {
+        use crate::SECP256K1;
+        let sk_data = hex!(
+            "e6dd32f8761625f105c39a39f19370b3521d845a12456d60ce44debd0a362641"
+        );
+        let sk = SecretKey::from_slice(&sk_data).unwrap();
+        let msg_data = hex!(
+            "a4965ca63b7d8562736ceec36dfa5a11bf426eb65be8ea3f7a49ae363032da0d"
+        );
+        let msg = Message::from_digest(msg_data);
+
+        // Check usage as explicit parameter
+        let pk = PublicKey::from_secret_key(SECP256K1, &sk);
+
+        // Check usage as self
+        let sig = SECP256K1.sign_ecdsa(&msg, &sk);
+        assert!(SECP256K1.verify_ecdsa(&msg, &sig, &pk).is_ok());
+    }
+}
+
+#[cfg(bench)]
+#[cfg(all(feature = "rand", feature = "std"))]
+mod benches {
+    use rand::rngs::mock::StepRng;
+    use test::{black_box, Bencher};
+
+    use super::{Message, Secp256k1};
+
+    #[bench]
+    pub fn generate(bh: &mut Bencher) {
+        let s = Secp256k1::new();
+        let mut r = StepRng::new(1, 1);
+        bh.iter(|| {
+            let (sk, pk) = s.generate_keypair(&mut r);
+            black_box(sk);
+            black_box(pk);
+        });
+    }
+
+    #[bench]
+    pub fn bench_sign_ecdsa(bh: &mut Bencher) {
+        let s = Secp256k1::new();
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest(msg);
+        let (sk, _) = s.generate_keypair(&mut rand::thread_rng());
+
+        bh.iter(|| {
+            let sig = s.sign_ecdsa(&msg, &sk);
+            black_box(sig);
+        });
+    }
+
+    #[bench]
+    pub fn bench_verify_ecdsa(bh: &mut Bencher) {
+        let s = Secp256k1::new();
+        let msg = crate::random_32_bytes(&mut rand::thread_rng());
+        let msg = Message::from_digest(msg);
+        let (sk, pk) = s.generate_keypair(&mut rand::thread_rng());
+        let sig = s.sign_ecdsa(&msg, &sk);
+
+        bh.iter(|| {
+            let res = s.verify_ecdsa(&msg, &sig, &pk).unwrap();
+            black_box(res);
+        });
+    }
+}
diff --git a/modules/ecash-secp256k1/src/macros.rs b/modules/ecash-secp256k1/src/macros.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/macros.rs
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: CC0-1.0
+
+/// Implement methods and traits for types that contain an inner array.
+#[macro_export]
+macro_rules! impl_array_newtype {
+    ($thing:ident, $ty:ty, $len:expr) => {
+        impl AsRef<[$ty; $len]> for $thing {
+            #[inline]
+            /// Gets a reference to the underlying array
+            fn as_ref(&self) -> &[$ty; $len] {
+                let &$thing(ref dat) = self;
+                dat
+            }
+        }
+
+        impl<I> core::ops::Index<I> for $thing
+        where
+            [$ty]: core::ops::Index<I>,
+        {
+            type Output = <[$ty] as core::ops::Index<I>>::Output;
+
+            #[inline]
+            fn index(&self, index: I) -> &Self::Output {
+                &self.0[index]
+            }
+        }
+
+        impl $crate::ffi::CPtr for $thing {
+            type Target = $ty;
+
+            fn as_c_ptr(&self) -> *const Self::Target {
+                let &$thing(ref dat) = self;
+                dat.as_ptr()
+            }
+
+            fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
+                let &mut $thing(ref mut dat) = self;
+                dat.as_mut_ptr()
+            }
+        }
+    };
+}
+
+macro_rules! impl_pretty_debug {
+    ($thing:ident) => {
+        impl core::fmt::Debug for $thing {
+            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+                write!(f, "{}(", stringify!($thing))?;
+                for i in &self[..] {
+                    write!(f, "{:02x}", i)?;
+                }
+                f.write_str(")")
+            }
+        }
+    };
+}
+
+macro_rules! impl_non_secure_erase {
+    ($thing:ident, $target:tt, $value:expr) => {
+        impl $thing {
+            /// Attempts to erase the contents of the underlying array.
+            ///
+            /// Note, however, that the compiler is allowed to freely copy or
+            /// move the contents of this array to other places in memory.
+            /// Preventing this behavior is very subtle. For more discussion
+            /// on this, please see the documentation of the
+            /// [`zeroize`](https://docs.rs/zeroize) crate.
+            #[inline]
+            pub fn non_secure_erase(&mut self) {
+                secp256k1_sys::non_secure_erase_impl(&mut self.$target, $value);
+            }
+        }
+    };
+}
+
+/// Formats error. If `std` feature is OFF appends error source (delimited by `:
+/// `). We do this because `e.source()` is only available in std builds, without
+/// this macro the error source is lost for no-std builds.
+macro_rules! write_err {
+    ($writer:expr, $string:literal $(, $args:expr),*; $source:expr) => {
+        {
+            #[cfg(feature = "std")]
+            {
+                let _ = &$source;   // Prevents clippy warnings.
+                write!($writer, $string $(, $args)*)
+            }
+            #[cfg(not(feature = "std"))]
+            {
+                write!($writer, concat!($string, ": {}") $(, $args)*, $source)
+            }
+        }
+    }
+}
+
+/// Implements fast unstable comparison methods for `$ty`.
+macro_rules! impl_fast_comparisons {
+    ($ty:ident) => {
+        impl $ty {
+            /// Like `cmp::Cmp` but faster and with no guarantees across library
+            /// versions.
+            ///
+            /// The `Cmp` implementation for FFI types is stable but slow
+            /// because it first serializes `self` and `other` before
+            /// comparing them. This function provides a faster comparison
+            /// if you know that your types come from the same library version.
+            pub fn cmp_fast_unstable(
+                &self,
+                other: &Self,
+            ) -> core::cmp::Ordering {
+                self.0.cmp_fast_unstable(&other.0)
+            }
+
+            /// Like `cmp::Eq` but faster and with no guarantees across library
+            /// versions.
+            ///
+            /// The `Eq` implementation for FFI types is stable but slow because
+            /// it first serializes `self` and `other` before comparing
+            /// them. This function provides a faster equality check if you
+            /// know that your types come from the same library version.
+            pub fn eq_fast_unstable(&self, other: &Self) -> bool {
+                self.0.eq_fast_unstable(&other.0)
+            }
+        }
+    };
+}
diff --git a/modules/ecash-secp256k1/src/scalar.rs b/modules/ecash-secp256k1/src/scalar.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/scalar.rs
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Provides [`Scalar`] and related types.
+//!
+//! In elliptic curve cryptography scalars are non-point values that can be used
+//! to multiply points. The most common type of scalars are private keys.
+//! However not all scalars are private keys. They can even be public *values*.
+//! To make handling them safer and easier this module provides the `Scalar`
+//! type and related.
+
+use core::{fmt, ops};
+
+use crate::constants;
+
+/// Positive 256-bit integer guaranteed to be less than the secp256k1 curve
+/// order.
+///
+/// The difference between `SecretKey` and `Scalar` is that `Scalar` doesn't
+/// guarantee being securely usable as a private key.
+///
+/// **Warning: the operations on this type are NOT constant time!**
+/// Using this with secret values is not advised.
+// Internal represenation is big endian to match what `libsecp256k1` uses.
+// Also easier to implement comparison.
+// Debug impl omitted for now, the bytes may be secret
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Scalar([u8; 32]);
+impl_pretty_debug!(Scalar);
+impl_non_secure_erase!(Scalar, 0, [0u8; 32]);
+
+const MAX_RAW: [u8; 32] = [
+    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
+    0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x40,
+];
+
+impl Scalar {
+    /// Maximum valid value: `curve_order - 1`
+    pub const MAX: Scalar = Scalar(MAX_RAW);
+    /// Scalar representing `1`
+    pub const ONE: Scalar = Scalar(constants::ONE);
+    /// Scalar representing `0`
+    pub const ZERO: Scalar = Scalar(constants::ZERO);
+
+    /// Generates a random scalar
+    #[cfg(all(feature = "rand", feature = "std"))]
+    pub fn random() -> Self {
+        Self::random_custom(rand::thread_rng())
+    }
+
+    /// Generates a random scalar using supplied RNG
+    #[cfg(feature = "rand")]
+    pub fn random_custom<R: rand::Rng>(mut rng: R) -> Self {
+        let mut bytes = [0u8; 32];
+        loop {
+            rng.fill_bytes(&mut bytes);
+            // unlikely to go past MAX
+            if let Ok(scalar) = Scalar::from_be_bytes(bytes) {
+                break scalar;
+            }
+        }
+    }
+
+    /// Tries to deserialize from big endian bytes
+    ///
+    /// **Security warning:** this function is not constant time!
+    /// Passing secret data is not recommended.
+    ///
+    /// # Errors
+    ///
+    /// Returns error when the value is above the curve order.
+    pub fn from_be_bytes(value: [u8; 32]) -> Result<Self, OutOfRangeError> {
+        // Lexicographic ordering of arrays of the same length is same as
+        // ordering of BE numbers
+        if value <= MAX_RAW {
+            Ok(Scalar(value))
+        } else {
+            Err(OutOfRangeError {})
+        }
+    }
+
+    /// Tries to deserialize from little endian bytes
+    ///
+    /// **Security warning:** this function is not constant time!
+    /// Passing secret data is not recommended.
+    ///
+    /// # Errors
+    ///
+    /// Returns error when the value is above the curve order.
+    pub fn from_le_bytes(mut value: [u8; 32]) -> Result<Self, OutOfRangeError> {
+        value.reverse();
+        Self::from_be_bytes(value)
+    }
+
+    /// Serializes to big endian bytes
+    pub fn to_be_bytes(self) -> [u8; 32] {
+        self.0
+    }
+
+    /// Serializes to little endian bytes
+    pub fn to_le_bytes(self) -> [u8; 32] {
+        let mut res = self.0;
+        res.reverse();
+        res
+    }
+
+    // returns a reference to internal bytes
+    // non-public to not leak the internal representation
+    pub(crate) fn as_be_bytes(&self) -> &[u8; 32] {
+        &self.0
+    }
+
+    pub(crate) fn as_c_ptr(&self) -> *const u8 {
+        use secp256k1_sys::CPtr;
+
+        self.as_be_bytes().as_c_ptr()
+    }
+}
+
+impl<I> ops::Index<I> for Scalar
+where
+    [u8]: ops::Index<I>,
+{
+    type Output = <[u8] as ops::Index<I>>::Output;
+
+    #[inline]
+    fn index(&self, index: I) -> &Self::Output {
+        &self.0[index]
+    }
+}
+
+impl From<crate::SecretKey> for Scalar {
+    fn from(value: crate::SecretKey) -> Self {
+        Scalar(value.secret_bytes())
+    }
+}
+
+/// Error returned when the value of scalar is invalid - larger than the curve
+/// order.
+// Intentionally doesn't implement `Copy` to improve forward compatibility.
+// Same reason for `non_exhaustive`.
+#[allow(missing_copy_implementations)]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+#[non_exhaustive]
+pub struct OutOfRangeError {}
+
+impl fmt::Display for OutOfRangeError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt("the value is not a member of secp256k1 field", f)
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for OutOfRangeError {}
diff --git a/modules/ecash-secp256k1/src/schnorr.rs b/modules/ecash-secp256k1/src/schnorr.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/schnorr.rs
@@ -0,0 +1,1069 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Support for schnorr signatures.
+
+use core::{fmt, ptr, str};
+
+#[cfg(feature = "rand")]
+use rand::{CryptoRng, Rng};
+use secp256k1_sys::SchnorrSigExtraParams;
+
+use crate::ffi::{self, CPtr};
+use crate::key::{Keypair, XOnlyPublicKey};
+#[cfg(feature = "global-context")]
+use crate::SECP256K1;
+use crate::{constants, from_hex, Error, Secp256k1, Signing, Verification};
+
+/// Represents a schnorr signature.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Signature([u8; constants::SCHNORR_SIGNATURE_SIZE]);
+impl_array_newtype!(Signature, u8, constants::SCHNORR_SIGNATURE_SIZE);
+impl_pretty_debug!(Signature);
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for Signature {
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+        if s.is_human_readable() {
+            s.collect_str(self)
+        } else {
+            s.serialize_bytes(&self[..])
+        }
+    }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for Signature {
+    fn deserialize<D: serde::Deserializer<'de>>(
+        d: D,
+    ) -> Result<Self, D::Error> {
+        if d.is_human_readable() {
+            d.deserialize_str(super::serde_util::FromStrVisitor::new(
+                "a hex string representing 64 byte schnorr signature",
+            ))
+        } else {
+            d.deserialize_bytes(super::serde_util::BytesVisitor::new(
+                "raw 64 bytes schnorr signature",
+                Signature::from_slice,
+            ))
+        }
+    }
+}
+
+impl fmt::LowerHex for Signature {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        for ch in &self.0[..] {
+            write!(f, "{:02x}", ch)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for Signature {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
+impl str::FromStr for Signature {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Signature, Error> {
+        let mut res = [0u8; constants::SCHNORR_SIGNATURE_SIZE];
+        match from_hex(s, &mut res) {
+            Ok(constants::SCHNORR_SIGNATURE_SIZE) => {
+                Ok(Signature::from_byte_array(res))
+            }
+            _ => Err(Error::InvalidSignature),
+        }
+    }
+}
+
+impl Signature {
+    /// Construct a `Signature` from a 64 bytes array.
+    #[inline]
+    pub fn from_byte_array(
+        sig: [u8; constants::SCHNORR_SIGNATURE_SIZE],
+    ) -> Self {
+        Self(sig)
+    }
+
+    /// Creates a `Signature` directly from a slice.
+    #[deprecated(since = "TBD", note = "Use `from_byte_array` instead.")]
+    #[inline]
+    pub fn from_slice(data: &[u8]) -> Result<Signature, Error> {
+        match data.len() {
+            constants::SCHNORR_SIGNATURE_SIZE => {
+                let mut ret = [0u8; constants::SCHNORR_SIGNATURE_SIZE];
+                ret[..].copy_from_slice(data);
+                Ok(Signature(ret))
+            }
+            _ => Err(Error::InvalidSignature),
+        }
+    }
+
+    /// Returns a signature as a byte array.
+    #[deprecated(since = "0.30.0", note = "Use `to_byte_array` instead.")]
+    pub fn serialize(&self) -> [u8; constants::SCHNORR_SIGNATURE_SIZE] {
+        self.0
+    }
+
+    /// Returns a signature as a byte array.
+    #[inline]
+    pub fn to_byte_array(self) -> [u8; constants::SCHNORR_SIGNATURE_SIZE] {
+        self.0
+    }
+
+    /// Returns a signature as a byte array.
+    #[inline]
+    pub fn as_byte_array(&self) -> &[u8; constants::SCHNORR_SIGNATURE_SIZE] {
+        &self.0
+    }
+
+    /// Verifies a schnorr signature for `msg` using `pk` and the global
+    /// [`SECP256K1`] context.
+    #[inline]
+    #[cfg(feature = "global-context")]
+    pub fn verify(&self, msg: &[u8], pk: &XOnlyPublicKey) -> Result<(), Error> {
+        SECP256K1.verify_schnorr(self, msg, pk)
+    }
+}
+
+impl<C: Signing> Secp256k1<C> {
+    fn sign_schnorr_helper(
+        &self,
+        msg: &[u8],
+        keypair: &Keypair,
+        nonce_data: *const ffi::types::c_uchar,
+    ) -> Signature {
+        unsafe {
+            let mut sig = [0u8; constants::SCHNORR_SIGNATURE_SIZE];
+            let extra = SchnorrSigExtraParams::new(None, nonce_data.cast());
+            assert_eq!(
+                1,
+                ffi::secp256k1_schnorrsig_sign_custom(
+                    self.ctx.as_ptr(),
+                    sig.as_mut_c_ptr(),
+                    msg.as_c_ptr(),
+                    msg.len(),
+                    keypair.as_c_ptr(),
+                    &extra,
+                )
+            );
+
+            Signature(sig)
+        }
+    }
+
+    /// Creates a schnorr signature internally using the
+    /// [`rand::rngs::ThreadRng`] random number generator to generate the
+    /// auxiliary random data.
+    #[cfg(all(feature = "rand", feature = "std"))]
+    pub fn sign_schnorr(&self, msg: &[u8], keypair: &Keypair) -> Signature {
+        self.sign_schnorr_with_rng(msg, keypair, &mut rand::thread_rng())
+    }
+
+    /// Creates a schnorr signature without using any auxiliary random data.
+    pub fn sign_schnorr_no_aux_rand(
+        &self,
+        msg: &[u8],
+        keypair: &Keypair,
+    ) -> Signature {
+        self.sign_schnorr_helper(msg, keypair, ptr::null())
+    }
+
+    /// Creates a schnorr signature using the given auxiliary random data.
+    pub fn sign_schnorr_with_aux_rand(
+        &self,
+        msg: &[u8],
+        keypair: &Keypair,
+        aux_rand: &[u8; 32],
+    ) -> Signature {
+        self.sign_schnorr_helper(
+            msg,
+            keypair,
+            aux_rand.as_c_ptr() as *const ffi::types::c_uchar,
+        )
+    }
+
+    /// Creates a schnorr signature using the given random number generator to
+    /// generate the auxiliary random data.
+    #[cfg(feature = "rand")]
+    pub fn sign_schnorr_with_rng<R: Rng + CryptoRng>(
+        &self,
+        msg: &[u8],
+        keypair: &Keypair,
+        rng: &mut R,
+    ) -> Signature {
+        let mut aux = [0u8; 32];
+        rng.fill_bytes(&mut aux);
+        self.sign_schnorr_helper(
+            msg,
+            keypair,
+            aux.as_c_ptr() as *const ffi::types::c_uchar,
+        )
+    }
+}
+
+impl<C: Verification> Secp256k1<C> {
+    /// Verifies a schnorr signature.
+    pub fn verify_schnorr(
+        &self,
+        sig: &Signature,
+        msg: &[u8],
+        pubkey: &XOnlyPublicKey,
+    ) -> Result<(), Error> {
+        unsafe {
+            let ret = ffi::secp256k1_schnorrsig_verify(
+                self.ctx.as_ptr(),
+                sig.as_c_ptr(),
+                msg.as_c_ptr(),
+                msg.len(),
+                pubkey.as_c_ptr(),
+            );
+
+            if ret == 1 {
+                Ok(())
+            } else {
+                Err(Error::IncorrectSignature)
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+#[allow(unused_imports)]
+mod tests {
+    use core::str::FromStr;
+
+    #[cfg(all(feature = "rand", feature = "std"))]
+    use rand::rngs::ThreadRng;
+    #[cfg(target_arch = "wasm32")]
+    use wasm_bindgen_test::wasm_bindgen_test as test;
+
+    use super::*;
+    use crate::schnorr::{Keypair, Signature, XOnlyPublicKey};
+    use crate::Error::InvalidPublicKey;
+    use crate::{constants, from_hex, Message, Secp256k1, SecretKey};
+
+    #[cfg(all(not(secp256k1_fuzz), feature = "alloc"))]
+    macro_rules! hex_32 {
+        ($hex:expr) => {{
+            let mut result = [0u8; 32];
+            from_hex($hex, &mut result).expect("valid hex string");
+            result
+        }};
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn schnorr_sign_with_aux_rand_verify() {
+        sign_helper(|secp, msg, seckey, rng| {
+            let aux_rand = crate::random_32_bytes(rng);
+            secp.sign_schnorr_with_aux_rand(msg, seckey, &aux_rand)
+        })
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn schnor_sign_with_rng_verify() {
+        sign_helper(|secp, msg, seckey, rng| {
+            secp.sign_schnorr_with_rng(msg, seckey, rng)
+        })
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn schnorr_sign_verify() {
+        sign_helper(|secp, msg, seckey, _| secp.sign_schnorr(msg, seckey))
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn schnorr_sign_no_aux_rand_verify() {
+        sign_helper(|secp, msg, seckey, _| {
+            secp.sign_schnorr_no_aux_rand(msg, seckey)
+        })
+    }
+
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn sign_helper(
+        sign: fn(
+            &Secp256k1<crate::All>,
+            &[u8],
+            &Keypair,
+            &mut ThreadRng,
+        ) -> Signature,
+    ) {
+        let secp = Secp256k1::new();
+
+        let mut rng = rand::thread_rng();
+        let kp = Keypair::new(&secp, &mut rng);
+        let (pk, _parity) = kp.x_only_public_key();
+
+        for _ in 0..100 {
+            let msg = crate::random_32_bytes(&mut rand::thread_rng());
+
+            let sig = sign(&secp, &msg, &kp, &mut rng);
+
+            assert!(secp.verify_schnorr(&sig, &msg, &pk).is_ok());
+        }
+    }
+
+    #[test]
+    #[cfg(feature = "alloc")]
+    #[cfg(not(secp256k1_fuzz))] // fixed sig vectors can't work with fuzz-sigs
+    fn schnorr_sign() {
+        let secp = Secp256k1::new();
+
+        let msg = hex_32!(
+            "E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"
+        );
+        let sk = Keypair::from_seckey_str(
+            &secp,
+            "688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF",
+        )
+        .unwrap();
+        let aux_rand: [u8; 32] = hex_32!(
+            "02CCE08E913F22A36C5648D6405A2C7C50106E7AA2F1649E381C7F09D16B80AB"
+        );
+        let expected_sig = Signature::from_str(
+            "6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE50\
+             77C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8"
+        )
+        .unwrap();
+
+        let sig = secp.sign_schnorr_with_aux_rand(&msg, &sk, &aux_rand);
+
+        assert_eq!(expected_sig, sig);
+    }
+
+    #[test]
+    #[cfg(not(secp256k1_fuzz))] // fixed sig vectors can't work with fuzz-sigs
+    #[cfg(feature = "alloc")]
+    fn schnorr_verify() {
+        let secp = Secp256k1::new();
+
+        let msg = hex_32!(
+            "E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"
+        );
+        let sig = Signature::from_str(
+            "6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE50\
+             77C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8"
+        )
+        .unwrap();
+        let pubkey = XOnlyPublicKey::from_str(
+            "B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390",
+        )
+        .unwrap();
+
+        assert!(secp.verify_schnorr(&sig, &msg, &pubkey).is_ok());
+    }
+
+    #[test]
+    fn test_serialize() {
+        let sig = Signature::from_str(
+            "6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE50\
+             77C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8"
+        )
+        .unwrap();
+        let sig_bytes = sig.to_byte_array();
+        let bytes = [
+            100, 112, 253, 19, 3, 221, 164, 253, 167, 23, 185, 131, 113, 83,
+            194, 74, 110, 171, 55, 113, 131, 252, 67, 143, 147, 158, 14, 210,
+            182, 32, 233, 238, 80, 119, 196, 168, 184, 220, 162, 137, 99, 215,
+            114, 169, 79, 95, 13, 223, 89, 142, 28, 71, 193, 55, 249, 25, 51,
+            39, 76, 124, 62, 218, 220, 232,
+        ];
+        assert_eq!(sig_bytes, bytes);
+    }
+
+    #[test]
+    fn test_pubkey_from_slice() {
+        assert_eq!(XOnlyPublicKey::from_slice(&[]), Err(InvalidPublicKey));
+        assert_eq!(
+            XOnlyPublicKey::from_slice(&[1, 2, 3]),
+            Err(InvalidPublicKey)
+        );
+        let pk = XOnlyPublicKey::from_slice(&[
+            0xB3, 0x3C, 0xC9, 0xED, 0xC0, 0x96, 0xD0, 0xA8, 0x34, 0x16, 0x96,
+            0x4B, 0xD3, 0xC6, 0x24, 0x7B, 0x8F, 0xEC, 0xD2, 0x56, 0xE4, 0xEF,
+            0xA7, 0x87, 0x0D, 0x2C, 0x85, 0x4B, 0xDE, 0xB3, 0x33, 0x90,
+        ]);
+        assert!(pk.is_ok());
+    }
+
+    #[test]
+    #[cfg(all(feature = "rand", feature = "std"))]
+    fn test_pubkey_serialize_roundtrip() {
+        let secp = Secp256k1::new();
+        let kp = Keypair::new(&secp, &mut rand::thread_rng());
+        let (pk, _parity) = kp.x_only_public_key();
+
+        let ser = pk.serialize();
+        let pubkey2 = XOnlyPublicKey::from_slice(&ser).unwrap();
+        assert_eq!(pk, pubkey2);
+    }
+
+    #[test]
+    #[cfg(feature = "alloc")]
+    fn test_xonly_key_extraction() {
+        let secp = Secp256k1::new();
+        let sk_str =
+            "688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF";
+        let keypair = Keypair::from_seckey_str(&secp, sk_str).unwrap();
+        let sk = SecretKey::from_keypair(&keypair);
+        assert_eq!(SecretKey::from_str(sk_str).unwrap(), sk);
+        let pk = crate::key::PublicKey::from_keypair(&keypair);
+        assert_eq!(crate::key::PublicKey::from_secret_key(&secp, &sk), pk);
+        let (xpk, _parity) = keypair.x_only_public_key();
+        assert_eq!(XOnlyPublicKey::from(pk), xpk);
+    }
+
+    #[test]
+    fn test_pubkey_from_bad_slice() {
+        // Bad sizes
+        assert_eq!(
+            XOnlyPublicKey::from_slice(
+                &[0; constants::SCHNORR_PUBLIC_KEY_SIZE - 1]
+            ),
+            Err(InvalidPublicKey)
+        );
+        assert_eq!(
+            XOnlyPublicKey::from_slice(
+                &[0; constants::SCHNORR_PUBLIC_KEY_SIZE + 1]
+            ),
+            Err(InvalidPublicKey)
+        );
+
+        // Bad parse
+        assert_eq!(
+            XOnlyPublicKey::from_slice(
+                &[0xff; constants::SCHNORR_PUBLIC_KEY_SIZE]
+            ),
+            Err(InvalidPublicKey)
+        );
+        // In fuzzing mode restrictions on public key validity are much more
+        // relaxed, thus the invalid check below is expected to fail.
+        #[cfg(not(secp256k1_fuzz))]
+        assert_eq!(
+            XOnlyPublicKey::from_slice(
+                &[0x55; constants::SCHNORR_PUBLIC_KEY_SIZE]
+            ),
+            Err(InvalidPublicKey)
+        );
+        assert_eq!(XOnlyPublicKey::from_slice(&[]), Err(InvalidPublicKey));
+    }
+
+    #[test]
+    #[cfg(feature = "std")]
+    fn test_pubkey_display_output() {
+        #[cfg(not(secp256k1_fuzz))]
+        let pk = {
+            let secp = Secp256k1::new();
+            static SK_BYTES: [u8; 32] = [
+                0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01,
+                0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff, 0xff, 0x00, 0x00,
+                0xff, 0xff, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+                0x63, 0x63,
+            ];
+
+            let kp = Keypair::from_seckey_slice(&secp, &SK_BYTES).expect("sk");
+
+            // In fuzzing mode secret->public key derivation is different, so
+            // hard-code the expected result.
+            let (pk, _parity) = kp.x_only_public_key();
+            pk
+        };
+        #[cfg(secp256k1_fuzz)]
+        let pk = XOnlyPublicKey::from_slice(&[
+            0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f, 0x1c, 0x97, 0x09,
+            0xe2, 0x30, 0x92, 0x06, 0x7d, 0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c,
+            0xd0, 0x54, 0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
+        ])
+        .expect("pk");
+
+        assert_eq!(
+            pk.to_string(),
+            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166"
+        );
+        assert_eq!(
+            XOnlyPublicKey::from_str(
+                "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1\
+                 66"
+            )
+            .unwrap(),
+            pk
+        );
+
+        /*assert!(XOnlyPublicKey::from_str(
+            "00000000000000000000000000000000000000000000000000000000000000000"
+        )
+        .is_err());
+        assert!(XOnlyPublicKey::from_str(
+            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd16601"
+        )
+        .is_err());
+        assert!(XOnlyPublicKey::from_str(
+            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd16"
+        )
+        .is_err());
+        assert!(XOnlyPublicKey::from_str(
+            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1"
+        )
+        .is_err());
+        assert!(XOnlyPublicKey::from_str(
+            "xx18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd1"
+        )
+        .is_err());
+
+        let long_str: String = "a".repeat(1024 * 1024);
+        assert!(XOnlyPublicKey::from_str(&long_str).is_err());*/
+    }
+
+    #[test]
+    // In fuzzing mode secret->public key derivation is different, so
+    // this test will never correctly derive the static pubkey.
+    #[cfg(not(secp256k1_fuzz))]
+    #[cfg(all(feature = "rand", feature = "alloc"))]
+    fn test_pubkey_serialize() {
+        use rand::rngs::mock::StepRng;
+        let secp = Secp256k1::new();
+        let kp = Keypair::new(&secp, &mut StepRng::new(1, 1));
+        let (pk, _parity) = kp.x_only_public_key();
+        assert_eq!(
+            &pk.serialize()[..],
+            &[
+                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
+            ][..]
+        );
+    }
+
+    #[cfg(not(secp256k1_fuzz))] // fixed sig vectors can't work with fuzz-sigs
+    #[test]
+    #[cfg(all(feature = "serde", feature = "alloc"))]
+    fn test_serde() {
+        use serde_test::{assert_tokens, Configure, Token};
+
+        let s = Secp256k1::new();
+
+        let msg = [1; 32];
+        let keypair = Keypair::from_seckey_slice(&s, &[2; 32]).unwrap();
+        let aux = [3u8; 32];
+        let sig = s.sign_schnorr_with_aux_rand(&msg, &keypair, &aux);
+        static SIG_BYTES: [u8; constants::SCHNORR_SIGNATURE_SIZE] = [
+            0x14, 0xd0, 0xbf, 0x1a, 0x89, 0x53, 0x50, 0x6f, 0xb4, 0x60, 0xf5,
+            0x8b, 0xe1, 0x41, 0xaf, 0x76, 0x7f, 0xd1, 0x12, 0x53, 0x5f, 0xb3,
+            0x92, 0x2e, 0xf2, 0x17, 0x30, 0x8e, 0x2c, 0x26, 0x70, 0x6f, 0x1e,
+            0xeb, 0x43, 0x2b, 0x3d, 0xba, 0x9a, 0x01, 0x08, 0x2f, 0x9e, 0x4d,
+            0x4e, 0xf5, 0x67, 0x8a, 0xd0, 0xd9, 0xd5, 0x32, 0xc0, 0xdf, 0xa9,
+            0x07, 0xb5, 0x68, 0x72, 0x2d, 0x0b, 0x01, 0x19, 0xba,
+        ];
+        static SIG_STR: &str = "\
+            14d0bf1a8953506fb460f58be141af767fd112535fb3922ef217308e2c26706f1ee\
+            b432b3dba9a01082f9e4d4ef5678ad0d9d532c0dfa907b568722d0b0119ba\
+        ";
+
+        static PK_BYTES: [u8; 32] = [
+            24, 132, 87, 129, 246, 49, 196, 143, 28, 151, 9, 226, 48, 146, 6,
+            125, 6, 131, 127, 48, 170, 12, 208, 84, 74, 200, 135, 254, 145,
+            221, 209, 102,
+        ];
+        static PK_STR: &str =
+            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166";
+        let pk = XOnlyPublicKey::from_slice(&PK_BYTES).unwrap();
+
+        assert_tokens(&sig.compact(), &[Token::BorrowedBytes(&SIG_BYTES[..])]);
+        assert_tokens(&sig.compact(), &[Token::Bytes(&SIG_BYTES[..])]);
+        assert_tokens(&sig.compact(), &[Token::ByteBuf(&SIG_BYTES[..])]);
+
+        assert_tokens(&sig.readable(), &[Token::BorrowedStr(SIG_STR)]);
+        assert_tokens(&sig.readable(), &[Token::Str(SIG_STR)]);
+        assert_tokens(&sig.readable(), &[Token::String(SIG_STR)]);
+
+        assert_tokens(
+            &pk.compact(),
+            &[
+                Token::Tuple { len: 32 },
+                Token::U8(24),
+                Token::U8(132),
+                Token::U8(87),
+                Token::U8(129),
+                Token::U8(246),
+                Token::U8(49),
+                Token::U8(196),
+                Token::U8(143),
+                Token::U8(28),
+                Token::U8(151),
+                Token::U8(9),
+                Token::U8(226),
+                Token::U8(48),
+                Token::U8(146),
+                Token::U8(6),
+                Token::U8(125),
+                Token::U8(6),
+                Token::U8(131),
+                Token::U8(127),
+                Token::U8(48),
+                Token::U8(170),
+                Token::U8(12),
+                Token::U8(208),
+                Token::U8(84),
+                Token::U8(74),
+                Token::U8(200),
+                Token::U8(135),
+                Token::U8(254),
+                Token::U8(145),
+                Token::U8(221),
+                Token::U8(209),
+                Token::U8(102),
+                Token::TupleEnd,
+            ],
+        );
+
+        assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
+        assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
+        assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
+    }
+
+    #[test]
+    #[cfg(feature = "alloc")]
+    #[cfg(not(secp256k1_fuzz))] // fixed sig vectors can't work with fuzz-sigs
+    fn bip340_test_vectors() {
+        struct TestVector {
+            secret_key: Option<[u8; 32]>,
+            public_key: [u8; 32],
+            aux_rand: Option<[u8; 32]>,
+            message: Vec<u8>,
+            signature: [u8; 64],
+            should_fail_verify: bool,
+        }
+        fn hex_arr<T: From<[u8; N]>, const N: usize>(s: &str) -> T {
+            let mut out = [0; N];
+            from_hex(s, &mut out).unwrap();
+            out.into()
+        }
+        let hex_vec = |s: &str| {
+            let mut v = vec![0u8; s.len() / 2];
+            from_hex(s, v.as_mut_slice()).unwrap();
+            v
+        };
+
+        let vectors = [
+            TestVector {
+                secret_key: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000003"
+                ),
+                public_key: hex_arr(
+                    "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BC\
+                     E036F9"
+                ),
+                aux_rand: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000000"
+                ),
+                message: hex_vec(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000000"
+                ),
+                signature: hex_arr(
+                    "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2D\
+                     CA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4\
+                     900D310536C0"
+                ),
+                should_fail_verify: false,
+            },
+            TestVector {
+                secret_key: hex_arr(
+                    "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D90451\
+                     90CFEF"
+                ),
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000001"
+                ),
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"),
+                signature: hex_arr(
+                    "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78\
+                     DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95\
+                     F6DE339E4B0A"
+                ),
+                should_fail_verify: false,
+            },
+            TestVector {
+                secret_key: hex_arr(
+                    "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B\
+                     14E5C9"
+                ),
+                public_key: hex_arr(
+                    "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969\
+                     774EB8"
+                ),
+                aux_rand: hex_arr(
+                    "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798\
+                     E6D906"
+                ),
+                message: hex_vec(
+                    "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A5\
+                     08B75C"
+                ),
+                signature: hex_arr(
+                    "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD\
+                     313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E\
+                     03674A6F3FB7"
+                ),
+                should_fail_verify: false,
+            },
+            TestVector {
+                secret_key: hex_arr(
+                    "0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2\
+                     401710"
+                ),
+                public_key: hex_arr(
+                    "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160\
+                     D8F517"
+                ),
+                aux_rand: hex_arr(
+                    "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\
+                     FFFFFF"
+                ),
+                message: hex_vec(
+                    "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\
+                     FFFFFF"
+                ),
+                signature: hex_arr(
+                    "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC96\
+                     37D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC\
+                     5922EFC66EA3"
+                ),
+                should_fail_verify: false,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D2\
+                     2DC7B9"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9\
+                     696703"
+                ),
+                signature: hex_arr(
+                    "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D679\
+                     5F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B\
+                     07D28308D7F4"
+                ),
+                should_fail_verify: false,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A\
+                     2D4A34"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E\
+                     17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D\
+                     5F7FC407D39B"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A1460\
+                     2975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAF\
+                     A34B1AC553E2"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0\
+                     E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA\
+                     5134FCCDB2BD"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E\
+                     177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834\
+                     FF0D0C2E6DA6"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C\
+                     6425BD186051"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     0000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780\
+                     D5A1837CF197"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F82204\
+                     29BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D\
+                     5F7FC407D39B"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFF\
+                     FFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D\
+                     5F7FC407D39B"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50\
+                     2BA659"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                    4E6C89"
+                ),
+                signature: hex_arr(
+                    "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E\
+                     177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD2\
+                     5E8CD0364141"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: None,
+                public_key: hex_arr(
+                    "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D\
+                     6FD117"
+                ),
+                aux_rand: None,
+                message: hex_vec(
+                    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC\
+                     4E6C89"
+                ),
+                signature: hex_arr(
+                    "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E\
+                     17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D\
+                     5F7FC407D39B"
+                ),
+                should_fail_verify: true,
+            },
+            TestVector {
+                secret_key: hex_arr(
+                    "0340034003400340034003400340034003400340034003400340034003\
+                     400340"
+                ),
+                public_key: hex_arr(
+                    "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D\
+                     6FD117"
+                ),
+                aux_rand: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000000"
+                ),
+                message: hex_vec(""),
+                signature: hex_arr(
+                    "71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A5589\
+                     5464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF\
+                     0574E427AB63"
+                ),
+                should_fail_verify: false,
+            },
+              TestVector {
+                secret_key: hex_arr(
+                    "0340034003400340034003400340034003400340034003400340034003\
+                     400340"
+                ),
+                public_key: hex_arr(
+                    "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D\
+                     6FD117"
+                ),
+                aux_rand: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000000"
+                ),
+                message: hex_vec("11"),
+                signature: hex_arr(
+                    "08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134\
+                     C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14\
+                     564CEC2BACBF"
+                ),
+                should_fail_verify: false,
+            },
+            TestVector {
+                secret_key: hex_arr(
+                    "0340034003400340034003400340034003400340034003400340034003\
+                     400340"
+                ),
+                public_key: hex_arr(
+                    "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D\
+                     6FD117"
+                ),
+                aux_rand: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000000"
+                ),
+                message: hex_vec("0102030405060708090A0B0C0D0E0F1011"),
+                signature: hex_arr(
+                    "5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B5\
+                     0AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C\
+                     5566A97EA5A5"
+                ),
+                should_fail_verify: false,
+            },
+            TestVector {
+                secret_key: hex_arr(
+                    "0340034003400340034003400340034003400340034003400340034003\
+                     400340"
+                ),
+                public_key: hex_arr(
+                    "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D\
+                     6FD117"
+                ),
+                aux_rand: hex_arr(
+                    "0000000000000000000000000000000000000000000000000000000000\
+                     000000"
+                ),
+                message: hex_vec(
+                    "9999999999999999999999999999999999999999999999999999999999\
+                     9999999999999999999999999999999999999999999999999999999999\
+                     9999999999999999999999999999999999999999999999999999999999\
+                     99999999999999999999999999"
+                ),
+                signature: hex_arr(
+                    "403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163E\
+                     CA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894\
+                     CFD249F22367"
+                ),
+                should_fail_verify: false,
+            },
+        ];
+        let secp = Secp256k1::new();
+
+        for TestVector {
+            secret_key,
+            public_key,
+            aux_rand,
+            message,
+            signature,
+            should_fail_verify,
+        } in vectors
+        {
+            if let (Some(secret_key), Some(aux_rand)) = (secret_key, aux_rand) {
+                let keypair =
+                    Keypair::from_seckey_slice(&secp, &secret_key).unwrap();
+                assert_eq!(
+                    keypair.x_only_public_key().0.serialize(),
+                    public_key
+                );
+                let sig = secp
+                    .sign_schnorr_with_aux_rand(&message, &keypair, &aux_rand);
+                assert_eq!(sig.to_byte_array(), signature);
+            }
+            let sig = Signature::from_byte_array(signature);
+            let is_verified =
+                if let Ok(pubkey) = XOnlyPublicKey::from_slice(&public_key) {
+                    secp.verify_schnorr(&sig, &message, &pubkey).is_ok()
+                } else {
+                    false
+                };
+            assert_eq!(is_verified, !should_fail_verify);
+        }
+    }
+}
diff --git a/modules/ecash-secp256k1/src/schnorrabc.rs b/modules/ecash-secp256k1/src/schnorrabc.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/schnorrabc.rs
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Support for schnorr signatures.
+
+use core::ptr;
+
+#[cfg(feature = "rand")]
+use rand::{CryptoRng, Rng};
+
+use crate::ffi::{self, CPtr};
+use crate::schnorr::Signature;
+use crate::{
+    constants, Error, Message, PublicKey, Secp256k1, SecretKey, Signing,
+    Verification,
+};
+
+impl<C: Signing> Secp256k1<C> {
+    fn sign_schnorrabc_helper(
+        &self,
+        msg: &Message,
+        seckey: &SecretKey,
+        nonce_data: *const ffi::types::c_void,
+    ) -> Signature {
+        unsafe {
+            let mut sig = [0u8; constants::SCHNORR_SIGNATURE_SIZE];
+            assert_eq!(
+                1,
+                ffi::secp256k1_schnorr_sign(
+                    self.ctx.as_ptr(),
+                    sig.as_mut_c_ptr(),
+                    msg.as_c_ptr(),
+                    seckey.as_c_ptr(),
+                    ffi::secp256k1_nonce_function_default,
+                    nonce_data
+                )
+            );
+
+            Signature::from_byte_array(sig)
+        }
+    }
+
+    /// Creates a schnorr signature internally using the
+    /// [`rand::rngs::ThreadRng`] random number generator to generate the
+    /// auxiliary random data.
+    #[cfg(all(feature = "rand", feature = "std"))]
+    pub fn sign_schnorrabc(
+        &self,
+        msg: &Message,
+        seckey: &SecretKey,
+    ) -> Signature {
+        let mut rng = rand::thread_rng();
+        self.sign_schnorrabc_with_rng(msg, seckey, &mut rng)
+    }
+
+    /// Create a schnorr signature without using any auxiliary random data.
+    pub fn sign_schnorrabc_no_aux_rand(
+        &self,
+        msg: &Message,
+        seckey: &SecretKey,
+    ) -> Signature {
+        self.sign_schnorrabc_helper(msg, seckey, ptr::null())
+    }
+
+    /// Creates a schnorr signature using the given auxiliary random data.
+    pub fn sign_schnorrabc_with_aux_rand(
+        &self,
+        msg: &Message,
+        seckey: &SecretKey,
+        aux_rand: &[u8; 32],
+    ) -> Signature {
+        self.sign_schnorrabc_helper(
+            msg,
+            seckey,
+            aux_rand.as_c_ptr() as *const ffi::types::c_void,
+        )
+    }
+
+    /// Creates a schnorr signature using the given random number generator to
+    /// generate the auxiliary random data.
+    #[cfg(feature = "rand")]
+    pub fn sign_schnorrabc_with_rng<R: Rng + CryptoRng>(
+        &self,
+        msg: &Message,
+        seckey: &SecretKey,
+        rng: &mut R,
+    ) -> Signature {
+        let mut aux = [0u8; 32];
+        rng.fill_bytes(&mut aux);
+        self.sign_schnorrabc_helper(
+            msg,
+            seckey,
+            aux.as_c_ptr() as *const ffi::types::c_void,
+        )
+    }
+}
+
+impl<C: Verification> Secp256k1<C> {
+    /// Verifies a schnorr signature.
+    pub fn verify_schnorrabc(
+        &self,
+        sig: &Signature,
+        msg: &Message,
+        pubkey: &PublicKey,
+    ) -> Result<(), Error> {
+        unsafe {
+            let ret = ffi::secp256k1_schnorr_verify(
+                self.ctx.as_ptr(),
+                sig.as_c_ptr(),
+                msg.as_c_ptr(),
+                pubkey.as_c_ptr(),
+            );
+
+            if ret == 1 {
+                Ok(())
+            } else {
+                Err(Error::IncorrectSignature)
+            }
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+#[cfg(test)]
+mod tests {
+    #[cfg(not(secp256k1_fuzz))]
+    macro_rules! hex_32 {
+        ($hex:expr) => {{
+            let mut result = [0; 32];
+            from_hex($hex, &mut result).expect("valid hex string");
+            result
+        }};
+    }
+
+    #[cfg(feature = "rand")]
+    fn test_sign_schnorrabc_helper(
+        sign: fn(
+            &crate::Secp256k1<crate::All>,
+            &crate::Message,
+            &crate::SecretKey,
+            &mut rand::rngs::ThreadRng,
+        ) -> crate::schnorr::Signature,
+    ) {
+        use rand::{thread_rng, RngCore};
+
+        use crate::{All, Message, PublicKey, Secp256k1, SecretKey};
+        let secp = Secp256k1::<All>::new();
+
+        let mut rng = thread_rng();
+        let seckey = SecretKey::new(&mut rng);
+        let pubkey = PublicKey::from_secret_key(&secp, &seckey);
+        let mut msg = [0; 32];
+
+        for _ in 0..100 {
+            rng.fill_bytes(&mut msg);
+            let msg = Message::from_digest(msg);
+
+            let sig = sign(&secp, &msg, &seckey, &mut rng);
+
+            assert!(secp.verify_schnorrabc(&sig, &msg, &pubkey).is_ok());
+        }
+    }
+
+    #[cfg(feature = "rand")]
+    #[test]
+    fn test_sign_schnorrabc_with_aux_rand_verify() {
+        test_sign_schnorrabc_helper(|secp, msg, seckey, rng| {
+            use rand::RngCore;
+            let mut aux_rand = [0; 32];
+            rng.fill_bytes(&mut aux_rand);
+            secp.sign_schnorrabc_with_aux_rand(msg, seckey, &aux_rand)
+        })
+    }
+
+    #[cfg(feature = "rand")]
+    #[test]
+    fn test_sign_schnorrabc_with_rng_verify() {
+        test_sign_schnorrabc_helper(|secp, msg, seckey, mut rng| {
+            secp.sign_schnorrabc_with_rng(msg, seckey, &mut rng)
+        })
+    }
+
+    #[cfg(feature = "rand")]
+    #[test]
+    fn test_sign_schnorrabc_verify() {
+        test_sign_schnorrabc_helper(|secp, msg, seckey, _| {
+            secp.sign_schnorrabc(msg, seckey)
+        })
+    }
+
+    #[cfg(feature = "rand")]
+    #[test]
+    fn test_sign_schnorrabc_no_aux_rand_verify() {
+        test_sign_schnorrabc_helper(|secp, msg, seckey, _| {
+            secp.sign_schnorrabc_no_aux_rand(msg, seckey)
+        })
+    }
+
+    #[cfg(not(secp256k1_fuzz))]
+    #[test]
+    fn test_schnorrabc_sign() {
+        use std::str::FromStr;
+
+        use crate::{
+            from_hex, schnorr::Signature, Message, Secp256k1, SecretKey,
+        };
+        let secp = Secp256k1::<crate::All>::new();
+
+        let hex_msg = hex_32!(
+            "E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"
+        );
+        let msg = Message::from_digest(hex_msg);
+        let seckey: SecretKey =
+            "688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF"
+                .parse()
+                .unwrap();
+        let aux_rand: [u8; 32] = hex_32!(
+            "02CCE08E913F22A36C5648D6405A2C7C50106E7AA2F1649E381C7F09D16B80AB"
+        );
+        let expected_sig = Signature::from_str(
+            "EDA588A9DDA57D3003E7DC9FEE6637B963016D4E425202C47EA1F72408AC6EEDEF\
+             8E96112AEE39AA242AEB93D0D479FA0EABAA8C5606E7B72346C701B71B1210"
+        ).unwrap();
+
+        let sig = secp.sign_schnorrabc_with_aux_rand(&msg, &seckey, &aux_rand);
+
+        assert_eq!(expected_sig, sig);
+    }
+
+    #[cfg(not(secp256k1_fuzz))]
+    #[test]
+    fn test_schnorrabc_verify() {
+        use std::str::FromStr;
+
+        use crate::{
+            from_hex, schnorr::Signature, Message, PublicKey, Secp256k1,
+        };
+
+        let secp = Secp256k1::<crate::All>::new();
+
+        let hex_msg = hex_32!(
+            "E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614"
+        );
+        let msg = Message::from_digest(hex_msg);
+        let sig = Signature::from_str(
+            "EDA588A9DDA57D3003E7DC9FEE6637B963016D4E425202C47EA1F72408AC6EEDEF\
+             8E96112AEE39AA242AEB93D0D479FA0EABAA8C5606E7B72346C701B71B1210"
+        )
+        .unwrap();
+        let pubkey: PublicKey =
+            "03B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390\
+            ".parse().unwrap();
+
+        assert!(secp.verify_schnorrabc(&sig, &msg, &pubkey).is_ok());
+    }
+}
diff --git a/modules/ecash-secp256k1/src/secret.rs b/modules/ecash-secp256k1/src/secret.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/secret.rs
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Helpers for displaying secret values
+
+use core::fmt;
+
+use crate::constants::SECRET_KEY_SIZE;
+use crate::ecdh::SharedSecret;
+use crate::key::{Keypair, SecretKey};
+use crate::to_hex;
+macro_rules! impl_display_secret {
+    // Default hasher exists only in standard library and not alloc
+    ($thing:ident) => {
+        #[cfg(feature = "hashes")]
+        impl ::core::fmt::Debug for $thing {
+            fn fmt(
+                &self,
+                f: &mut ::core::fmt::Formatter,
+            ) -> ::core::fmt::Result {
+                use hashes::{sha256, Hash, HashEngine};
+
+                let tag = "rust-secp256k1DEBUG";
+
+                let mut engine = sha256::Hash::engine();
+                let tag_hash = sha256::Hash::hash(tag.as_bytes());
+                engine.input(&tag_hash.as_ref());
+                engine.input(&tag_hash.as_ref());
+                engine.input(&self.secret_bytes());
+                let hash = sha256::Hash::from_engine(engine);
+
+                f.debug_tuple(stringify!($thing))
+                    .field(&format_args!("#{:.16}", hash))
+                    .finish()
+            }
+        }
+
+        #[cfg(not(feature = "hashes"))]
+        impl ::core::fmt::Debug for $thing {
+            fn fmt(
+                &self,
+                f: &mut ::core::fmt::Formatter,
+            ) -> ::core::fmt::Result {
+                write!(
+                    f,
+                    "<secret key; enable `hashes` feature of `secp256k1` to \
+                     display fingerprint>"
+                )
+            }
+        }
+    };
+}
+
+/// Helper struct for safely printing secrets (like [`SecretKey`] value).
+/// Formats the explicit byte value of the secret kept inside the type as a
+/// little-endian hexadecimal string using the provided formatter.
+///
+/// Secrets should not implement neither [`Debug`] and [`Display`] traits
+/// directly, and instead provide `fn display_secret<'a>(&'a self) ->
+/// DisplaySecret<'a>` function to be used in different display contexts (see
+/// "examples" below).
+///
+/// [`Display`]: fmt::Display
+/// [`Debug`]: fmt::Debug
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct DisplaySecret {
+    secret: [u8; SECRET_KEY_SIZE],
+}
+impl_non_secure_erase!(DisplaySecret, secret, [0u8; SECRET_KEY_SIZE]);
+
+impl fmt::Debug for DisplaySecret {
+    #[inline]
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let mut slice = [0u8; SECRET_KEY_SIZE * 2];
+        let hex = to_hex(&self.secret, &mut slice)
+            .expect("fixed-size hex serializer failed");
+        f.debug_tuple("DisplaySecret").field(&hex).finish()
+    }
+}
+
+impl fmt::Display for DisplaySecret {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        for byte in &self.secret {
+            write!(f, "{:02x}", byte)?;
+        }
+        Ok(())
+    }
+}
+
+impl SecretKey {
+    /// Formats the explicit byte value of the secret key kept inside the type
+    /// as a little-endian hexadecimal string using the provided formatter.
+    ///
+    /// This is the only method that outputs the actual secret key value, and,
+    /// thus, should be used with extreme caution.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(feature = "std")] {
+    /// # use std::str::FromStr;
+    /// use ecash_secp256k1::SecretKey;
+    /// let key = SecretKey::from_str(
+    ///     "0000000000000000000000000000000000000000000000000000000000000001",
+    /// )
+    /// .unwrap();
+    ///
+    /// // Normal debug hides value (`Display` not implemented for `SecretKey`).
+    /// // E.g., `format!("{:?}", key)` prints "SecretKey(#2518682f7819fb2d)".
+    ///
+    /// // Here we explicitly display the secret value:
+    /// assert_eq!(
+    ///     "0000000000000000000000000000000000000000000000000000000000000001",
+    ///     format!("{}", key.display_secret())
+    /// );
+    /// // Also, we can explicitly display with `Debug`:
+    /// assert_eq!(
+    ///     format!("{:?}", key.display_secret()),
+    ///     format!("DisplaySecret(\"{}\")", key.display_secret())
+    /// );
+    /// # }
+    /// ```
+    #[inline]
+    pub fn display_secret(&self) -> DisplaySecret {
+        DisplaySecret {
+            secret: self.secret_bytes(),
+        }
+    }
+}
+
+impl Keypair {
+    /// Formats the explicit byte value of the secret key kept inside the type
+    /// as a little-endian hexadecimal string using the provided formatter.
+    ///
+    /// This is the only method that outputs the actual secret key value, and,
+    /// thus, should be used with extreme precaution.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # #[cfg(feature = "std")] {
+    /// # use std::str::FromStr;
+    /// use ecash_secp256k1::{Keypair, Secp256k1, SecretKey};
+    ///
+    /// let secp = Secp256k1::new();
+    /// let key = SecretKey::from_str(
+    ///     "0000000000000000000000000000000000000000000000000000000000000001",
+    /// )
+    /// .unwrap();
+    /// let key = Keypair::from_secret_key(&secp, &key);
+    /// // Here we explicitly display the secret value:
+    /// assert_eq!(
+    ///     "0000000000000000000000000000000000000000000000000000000000000001",
+    ///     format!("{}", key.display_secret())
+    /// );
+    /// // Also, we can explicitly display with `Debug`:
+    /// assert_eq!(
+    ///     format!("{:?}", key.display_secret()),
+    ///     format!("DisplaySecret(\"{}\")", key.display_secret())
+    /// );
+    /// # }
+    /// ```
+    #[inline]
+    pub fn display_secret(&self) -> DisplaySecret {
+        DisplaySecret {
+            secret: self.secret_bytes(),
+        }
+    }
+}
+
+impl SharedSecret {
+    /// Formats the explicit byte value of the shared secret kept inside the
+    /// type as a little-endian hexadecimal string using the provided
+    /// formatter.
+    ///
+    /// This is the only method that outputs the actual shared secret value,
+    /// and, thus, should be used with extreme caution.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #[cfg(not(secp256k1_fuzz))]
+    /// # #[cfg(feature = "std")] {
+    /// # use std::str::FromStr;
+    /// use ecash_secp256k1::{SecretKey, PublicKey};
+    /// use ecash_secp256k1::ecdh::SharedSecret;
+    ///
+    /// # let pk = PublicKey::from_slice(
+    /// #     &[3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41,
+    /// #       248, 140, 11, 3, 51, 41, 111, 180, 110, 143, 114, 134, 88, 73,
+    /// #       198, 174, 52, 184, 78]
+    /// # ).expect("hard coded slice should parse correctly");
+    /// # let sk = SecretKey::from_str(
+    /// #    "57f0148f94d13095cfda539d0da0d1541304b678d8b36e243980aab4e1b7cead"
+    /// # ).unwrap();
+    ///
+    /// let secret = SharedSecret::new(&pk, &sk);
+    /// // Here we explicitly display the secret value:
+    /// assert_eq!(
+    ///     format!("{}", secret.display_secret()),
+    ///     "cf05ae7da039ddce6d56dd57d3000c6dd91c6f1695eae47e05389f11e2467043"
+    /// );
+    /// // Also, we can explicitly display with `Debug`:
+    /// assert_eq!(
+    ///     format!("{:?}", secret.display_secret()),
+    ///     format!("DisplaySecret(\"{}\")", secret.display_secret())
+    /// );
+    /// # }
+    /// ```
+    #[inline]
+    pub fn display_secret(&self) -> DisplaySecret {
+        DisplaySecret {
+            secret: self.secret_bytes(),
+        }
+    }
+}
diff --git a/modules/ecash-secp256k1/src/serde_util.rs b/modules/ecash-secp256k1/src/serde_util.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/src/serde_util.rs
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: CC0-1.0
+
+use core::fmt;
+use core::marker::PhantomData;
+use core::str::{self, FromStr};
+
+use serde::de;
+
+/// A serde visitor that works for `T`s implementing `FromStr`.
+pub struct FromStrVisitor<T> {
+    expectation: &'static str,
+    _pd: PhantomData<T>,
+}
+
+impl<T> FromStrVisitor<T> {
+    pub fn new(expectation: &'static str) -> Self {
+        FromStrVisitor {
+            expectation,
+            _pd: PhantomData,
+        }
+    }
+}
+
+impl<'de, T> de::Visitor<'de> for FromStrVisitor<T>
+where
+    T: FromStr,
+    <T as FromStr>::Err: fmt::Display,
+{
+    type Value = T;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(self.expectation)
+    }
+
+    fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
+        FromStr::from_str(v).map_err(E::custom)
+    }
+}
+
+pub struct BytesVisitor<F> {
+    expectation: &'static str,
+    parse_fn: F,
+}
+
+impl<F, T, Err> BytesVisitor<F>
+where
+    F: FnOnce(&[u8]) -> Result<T, Err>,
+    Err: fmt::Display,
+{
+    pub fn new(expectation: &'static str, parse_fn: F) -> Self {
+        BytesVisitor {
+            expectation,
+            parse_fn,
+        }
+    }
+}
+
+impl<'de, F, T, Err> de::Visitor<'de> for BytesVisitor<F>
+where
+    F: FnOnce(&[u8]) -> Result<T, Err>,
+    Err: fmt::Display,
+{
+    type Value = T;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(self.expectation)
+    }
+
+    fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
+        (self.parse_fn)(v).map_err(E::custom)
+    }
+}
+
+macro_rules! impl_tuple_visitor {
+    ($thing:ident, $len:expr) => {
+        pub(crate) struct $thing<F> {
+            expectation: &'static str,
+            parse_fn: F,
+        }
+
+        impl<F, T, E> $thing<F>
+        where
+            F: FnOnce(&[u8]) -> Result<T, E>,
+            E: fmt::Display,
+        {
+            pub fn new(expectation: &'static str, parse_fn: F) -> Self {
+                $thing {
+                    expectation,
+                    parse_fn,
+                }
+            }
+        }
+
+        impl<'de, F, T, E> de::Visitor<'de> for $thing<F>
+        where
+            F: FnOnce(&[u8]) -> Result<T, E>,
+            E: fmt::Display,
+        {
+            type Value = T;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str(self.expectation)
+            }
+
+            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
+            where
+                V: de::SeqAccess<'de>,
+            {
+                let mut bytes = [0u8; $len];
+
+                for (i, byte) in bytes.iter_mut().enumerate() {
+                    if let Some(value) = seq.next_element()? {
+                        *byte = value;
+                    } else {
+                        return Err(de::Error::invalid_length(i, &self));
+                    }
+                }
+                (self.parse_fn)(&bytes).map_err(de::Error::custom)
+            }
+        }
+    };
+}
+
+impl_tuple_visitor!(Tuple32Visitor, 32);
+impl_tuple_visitor!(Tuple33Visitor, 33);
diff --git a/modules/ecash-secp256k1/tests/serde.rs b/modules/ecash-secp256k1/tests/serde.rs
new file mode 100644
--- /dev/null
+++ b/modules/ecash-secp256k1/tests/serde.rs
@@ -0,0 +1,90 @@
+#![cfg(feature = "serde")]
+
+extern crate bincode;
+extern crate ecash_secp256k1 as secp256k1;
+extern crate serde_cbor;
+
+#[cfg(feature = "global-context")]
+use secp256k1::{Keypair, Secp256k1};
+use secp256k1::{PublicKey, SecretKey, XOnlyPublicKey};
+
+// Arbitrary key data.
+
+#[rustfmt::skip]
+static SK_BYTES: [u8; 32] = [
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
+    0x0f, 0x10, 0x1f, 0xa0, 0xa9, 0xaa, 0xaf, 0xff,
+];
+
+#[rustfmt::skip]
+static PK_BYTES: [u8; 33] = [
+    0x02,
+    0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
+    0x1c, 0x97, 0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d,
+    0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c, 0xd0, 0x54,
+    0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
+];
+
+#[rustfmt::skip]
+static XONLY_PK_BYTES: [u8; 32] = [
+    0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
+    0x1c, 0x97, 0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d,
+    0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c, 0xd0, 0x54,
+    0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
+];
+
+fn secret_key() -> SecretKey {
+    SecretKey::from_slice(&SK_BYTES).expect("failed to create sk from slice")
+}
+
+// Our current serde serialization implementation is only guaranteed to be fixed
+// width for bincode. https://docs.rs/bincode/latest/bincode/index.html
+#[test]
+fn bincode_secret_key() {
+    let sk = secret_key();
+    let ser = bincode::serialize(&sk).unwrap();
+
+    assert_eq!(ser, SK_BYTES);
+}
+
+#[test]
+fn bincode_public_key() {
+    let pk = PublicKey::from_slice(&PK_BYTES)
+        .expect("failed to create pk from slice");
+    let ser = bincode::serialize(&pk).unwrap();
+
+    assert_eq!(ser, &PK_BYTES as &[u8])
+}
+
+#[test]
+#[cfg(feature = "global-context")]
+fn bincode_keypair() {
+    let secp = Secp256k1::new();
+    let kp = Keypair::from_seckey_slice(&secp, &SK_BYTES)
+        .expect("failed to create keypair");
+    let ser = bincode::serialize(&kp).unwrap();
+
+    assert_eq!(ser, SK_BYTES);
+}
+
+#[test]
+fn bincode_x_only_public_key() {
+    let pk = XOnlyPublicKey::from_slice(&XONLY_PK_BYTES)
+        .expect("failed to create xonly pk from slice");
+    let ser = bincode::serialize(&pk).unwrap();
+
+    assert_eq!(ser, XONLY_PK_BYTES);
+}
+
+#[test]
+fn cbor() {
+    let sk = secret_key();
+    let e = serde_cbor::to_vec(&sk).unwrap();
+    // Secret key is 32 bytes. CBOR adds a byte of metadata for 20 of these
+    // bytes, (Apparently, any byte whose value is <24 gets an extra byte.)
+    // It also adds a 1-byte length prefix and a byte of metadata for the whole
+    // vector.
+    assert_eq!(e.len(), 54);
+}