mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 23:18:01 +00:00
794ee98049
Bumps [secp256k1](https://github.com/rust-bitcoin/rust-secp256k1) from 0.24.3 to 0.28.0. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/rust-bitcoin/rust-secp256k1/blob/master/CHANGELOG.md">secp256k1's changelog</a>.</em></p> <blockquote> <h1>0.28.0 - 2023-10-23</h1> <ul> <li>Add bindings to the ElligatorSwift implementation <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/627">#627</a></li> <li>Depend on recent release of <code>bitcoin_hashes</code> v0.13.0 <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/621">#621</a></li> <li>Add a verify function to <code>PublicKey</code> <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/618">#618</a></li> <li>Add serialize function for schnorr::Signature <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/607">#607</a></li> <li>Bump MSRV to 1.48 <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/595">#595</a></li> <li>Remove implementations of <code>PartialEq</code>, <code>Eq</code>, <code>PartialOrd</code>, <code>Ord</code>, and <code>Hash</code> from the <code>impl_array_newtype</code> macro. Users will now need to derive these traits if they are wanted.</li> </ul> <h1>0.27.0 - 2023-03-15</h1> <ul> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/588">Depend on newly release <code>bitcoin_hashes</code> v0.12</a>.</li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/578">Implement <code>Debug</code> trait for <code>Scalar</code> type</a>.</li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/582">Implement <code>insecure-erase</code></a>.</li> </ul> <h1>0.26.0 - 2202-12-19</h1> <ul> <li>Update libsecp25k1 to v0.2.0</li> </ul> <h1>0.25.0 - 2022-12-07</h1> <ul> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/548">Fix soundness issue with <code>preallocated_gen_new</code></a></li> <li>Update to <code>secp256k1-sys</code> <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/549">v0.7.0</a></li> <li>Use type system to <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/483">improve safety</a>.</li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/490">Change secp256k1-sys symbol names to 0_6_1</a>.</li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/499">Introduce <code>rustfmt</code></a> to the codebase.</li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/507">Make all raw pointer methods go through the CPtr trait</a>.</li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/518">Make comparison functions stable</a>.</li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/512">Remove</a> public constant <code>ONE_KEY</code> (consider using <code>FromStr</code> as a replacement).</li> </ul> <h1>0.24.1 - 2022-10-25</h1> <ul> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/issues/491">Fix broken deserialization logic of <code>KeyPair</code></a> that previously always panicked. After the patch deserialization only panics if neither the <code>global-context</code> nor the <code>alloc</code> (default) feature is active.</li> </ul> <h1>0.24.0 - 2022-07-20</h1> <ul> <li>Upgrade to new release of <a href="https://github.com/rust-bitcoin/bitcoin_hashes/releases/tag/0.11.0">bitcoin_hashes</a>.</li> </ul> <h1>0.23.4 - 2022-07-14</h1> <ul> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/474">Disable automatic rerandomization of contexts under WASM</a></li> </ul> <h1>0.23.3 - 2022-06-29</h1> <ul> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/465">Add must_use for mut self key manipulation methods</a></li> <li><a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/pull/466">Fix fuzzing feature guard</a></li> </ul> <h1>0.23.2 - 2022-06-27</h1> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/7de09c8050da12a13ef9ee3850597f69c887952d"><code>7de09c8</code></a> Merge <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/issues/256">rust-bitcoin/rust-secp256k1256</a>: Tracking PR for release: `secp256k1 v0...</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/3dc5b165401f249c01a88cec54061301cffd97a0"><code>3dc5b16</code></a> Bump version to v0.28.0</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/3aada83180beec2b9f5ab8e7b9280a5517d3bcde"><code>3aada83</code></a> Merge <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/issues/256">rust-bitcoin/rust-secp256k1256</a>: Add bindings to the ElligatorSwift imp...</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/39febcb866ce285d53052a3636602f04483aa710"><code>39febcb</code></a> Create rust-bidings</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/da4f67b274c3061717723a568cfb182e4e2e7cce"><code>da4f67b</code></a> Merge <a href="https://redirect.github.com/rust-bitcoin/rust-secp256k1/issues/256">rust-bitcoin/rust-secp256k1256</a>: Update vendored lib secp256k1 to v0.4.0</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/80b2a8d4aa6ffa72041d569eab2278cd8c1ace2a"><code>80b2a8d</code></a> Update vendored libsecp to v0.4.0</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/d2285c929a086276ce6d1670d795c49191e30c65"><code>d2285c9</code></a> ci: Remove MIPS* from CI</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/0d58f50d523b40a78de0b87146208e3ad338c8ba"><code>0d58f50</code></a> ci: generalize grp in "illegal callback" test</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/acf9ac13e9f8df84dd52d2f012cda7211a6af10c"><code>acf9ac1</code></a> delete <code>test_manual_create_destroy</code> test</li> <li><a href="https://github.com/rust-bitcoin/rust-secp256k1/commit/04ce50891bb0d49be5355f5c0d82db70d7dda65a"><code>04ce508</code></a> lib: fix bad unit test</li> <li>Additional commits viewable in <a href="https://github.com/rust-bitcoin/rust-secp256k1/compare/secp256k1-0.24.3...secp256k1-0.28.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
807 lines
24 KiB
Rust
807 lines
24 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Simple ECDSA secp256k1 API.
|
|
|
|
use codec::{Decode, Encode, MaxEncodedLen};
|
|
use scale_info::TypeInfo;
|
|
use sp_runtime_interface::pass_by::PassByInner;
|
|
|
|
#[cfg(feature = "serde")]
|
|
use crate::crypto::Ss58Codec;
|
|
use crate::crypto::{
|
|
ByteArray, CryptoType, CryptoTypeId, Derive, Public as TraitPublic, UncheckedFrom,
|
|
};
|
|
#[cfg(feature = "full_crypto")]
|
|
use crate::{
|
|
crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError},
|
|
hashing::blake2_256,
|
|
};
|
|
#[cfg(all(feature = "full_crypto", not(feature = "std")))]
|
|
use secp256k1::Secp256k1;
|
|
#[cfg(feature = "std")]
|
|
use secp256k1::SECP256K1;
|
|
#[cfg(feature = "full_crypto")]
|
|
use secp256k1::{
|
|
ecdsa::{RecoverableSignature, RecoveryId},
|
|
Message, PublicKey, SecretKey,
|
|
};
|
|
#[cfg(feature = "serde")]
|
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
|
#[cfg(all(not(feature = "std"), feature = "serde"))]
|
|
use sp_std::alloc::{format, string::String};
|
|
#[cfg(feature = "full_crypto")]
|
|
use sp_std::vec::Vec;
|
|
|
|
/// An identifier used to match public keys against ecdsa keys
|
|
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds");
|
|
|
|
/// The byte length of public key
|
|
pub const PUBLIC_KEY_SERIALIZED_SIZE: usize = 33;
|
|
|
|
/// The byte length of signature
|
|
pub const SIGNATURE_SERIALIZED_SIZE: usize = 65;
|
|
|
|
/// A secret seed (which is bytewise essentially equivalent to a SecretKey).
|
|
///
|
|
/// We need it as a different type because `Seed` is expected to be AsRef<[u8]>.
|
|
#[cfg(feature = "full_crypto")]
|
|
type Seed = [u8; 32];
|
|
|
|
/// The ECDSA compressed public key.
|
|
#[cfg_attr(feature = "full_crypto", derive(Hash))]
|
|
#[derive(
|
|
Clone,
|
|
Copy,
|
|
Encode,
|
|
Decode,
|
|
PassByInner,
|
|
MaxEncodedLen,
|
|
TypeInfo,
|
|
Eq,
|
|
PartialEq,
|
|
PartialOrd,
|
|
Ord,
|
|
)]
|
|
pub struct Public(pub [u8; PUBLIC_KEY_SERIALIZED_SIZE]);
|
|
|
|
impl crate::crypto::FromEntropy for Public {
|
|
fn from_entropy(input: &mut impl codec::Input) -> Result<Self, codec::Error> {
|
|
let mut result = Self([0u8; PUBLIC_KEY_SERIALIZED_SIZE]);
|
|
input.read(&mut result.0[..])?;
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
impl Public {
|
|
/// A new instance from the given 33-byte `data`.
|
|
///
|
|
/// NOTE: No checking goes on to ensure this is a real public key. Only use it if
|
|
/// you are certain that the array actually is a pubkey. GIGO!
|
|
pub fn from_raw(data: [u8; PUBLIC_KEY_SERIALIZED_SIZE]) -> Self {
|
|
Self(data)
|
|
}
|
|
|
|
/// Create a new instance from the given full public key.
|
|
///
|
|
/// This will convert the full public key into the compressed format.
|
|
#[cfg(feature = "std")]
|
|
pub fn from_full(full: &[u8]) -> Result<Self, ()> {
|
|
let pubkey = if full.len() == 64 {
|
|
// Tag it as uncompressed public key.
|
|
let mut tagged_full = [0u8; 65];
|
|
tagged_full[0] = 0x04;
|
|
tagged_full[1..].copy_from_slice(full);
|
|
secp256k1::PublicKey::from_slice(&tagged_full)
|
|
} else {
|
|
secp256k1::PublicKey::from_slice(full)
|
|
};
|
|
pubkey.map(|k| Self(k.serialize())).map_err(|_| ())
|
|
}
|
|
}
|
|
|
|
impl ByteArray for Public {
|
|
const LEN: usize = PUBLIC_KEY_SERIALIZED_SIZE;
|
|
}
|
|
|
|
impl TraitPublic for Public {}
|
|
|
|
impl Derive for Public {}
|
|
|
|
impl AsRef<[u8]> for Public {
|
|
fn as_ref(&self) -> &[u8] {
|
|
&self.0[..]
|
|
}
|
|
}
|
|
|
|
impl AsMut<[u8]> for Public {
|
|
fn as_mut(&mut self) -> &mut [u8] {
|
|
&mut self.0[..]
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&[u8]> for Public {
|
|
type Error = ();
|
|
|
|
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
|
|
if data.len() != Self::LEN {
|
|
return Err(())
|
|
}
|
|
let mut r = [0u8; Self::LEN];
|
|
r.copy_from_slice(data);
|
|
Ok(Self::unchecked_from(r))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "full_crypto")]
|
|
impl From<Pair> for Public {
|
|
fn from(x: Pair) -> Self {
|
|
x.public()
|
|
}
|
|
}
|
|
|
|
impl UncheckedFrom<[u8; PUBLIC_KEY_SERIALIZED_SIZE]> for Public {
|
|
fn unchecked_from(x: [u8; PUBLIC_KEY_SERIALIZED_SIZE]) -> Self {
|
|
Public(x)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "std")]
|
|
impl std::fmt::Display for Public {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "{}", self.to_ss58check())
|
|
}
|
|
}
|
|
|
|
impl sp_std::fmt::Debug for Public {
|
|
#[cfg(feature = "std")]
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
let s = self.to_ss58check();
|
|
write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&self.as_ref()), &s[0..8])
|
|
}
|
|
|
|
#[cfg(not(feature = "std"))]
|
|
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl Serialize for Public {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_str(&self.to_ss58check())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de> Deserialize<'de> for Public {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
Public::from_ss58check(&String::deserialize(deserializer)?)
|
|
.map_err(|e| de::Error::custom(format!("{:?}", e)))
|
|
}
|
|
}
|
|
|
|
/// A signature (a 512-bit value, plus 8 bits for recovery ID).
|
|
#[cfg_attr(feature = "full_crypto", derive(Hash))]
|
|
#[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)]
|
|
pub struct Signature(pub [u8; SIGNATURE_SERIALIZED_SIZE]);
|
|
|
|
impl ByteArray for Signature {
|
|
const LEN: usize = SIGNATURE_SERIALIZED_SIZE;
|
|
}
|
|
|
|
impl TryFrom<&[u8]> for Signature {
|
|
type Error = ();
|
|
|
|
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
|
|
if data.len() == SIGNATURE_SERIALIZED_SIZE {
|
|
let mut inner = [0u8; SIGNATURE_SERIALIZED_SIZE];
|
|
inner.copy_from_slice(data);
|
|
Ok(Signature(inner))
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl Serialize for Signature {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_str(&array_bytes::bytes2hex("", self))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de> Deserialize<'de> for Signature {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let signature_hex = array_bytes::hex2bytes(&String::deserialize(deserializer)?)
|
|
.map_err(|e| de::Error::custom(format!("{:?}", e)))?;
|
|
Signature::try_from(signature_hex.as_ref())
|
|
.map_err(|e| de::Error::custom(format!("{:?}", e)))
|
|
}
|
|
}
|
|
|
|
impl Clone for Signature {
|
|
fn clone(&self) -> Self {
|
|
let mut r = [0u8; SIGNATURE_SERIALIZED_SIZE];
|
|
r.copy_from_slice(&self.0[..]);
|
|
Signature(r)
|
|
}
|
|
}
|
|
|
|
impl Default for Signature {
|
|
fn default() -> Self {
|
|
Signature([0u8; SIGNATURE_SERIALIZED_SIZE])
|
|
}
|
|
}
|
|
|
|
impl From<Signature> for [u8; SIGNATURE_SERIALIZED_SIZE] {
|
|
fn from(v: Signature) -> [u8; SIGNATURE_SERIALIZED_SIZE] {
|
|
v.0
|
|
}
|
|
}
|
|
|
|
impl AsRef<[u8; SIGNATURE_SERIALIZED_SIZE]> for Signature {
|
|
fn as_ref(&self) -> &[u8; SIGNATURE_SERIALIZED_SIZE] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl AsRef<[u8]> for Signature {
|
|
fn as_ref(&self) -> &[u8] {
|
|
&self.0[..]
|
|
}
|
|
}
|
|
|
|
impl AsMut<[u8]> for Signature {
|
|
fn as_mut(&mut self) -> &mut [u8] {
|
|
&mut self.0[..]
|
|
}
|
|
}
|
|
|
|
impl sp_std::fmt::Debug for Signature {
|
|
#[cfg(feature = "std")]
|
|
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
|
write!(f, "{}", crate::hexdisplay::HexDisplay::from(&self.0))
|
|
}
|
|
|
|
#[cfg(not(feature = "std"))]
|
|
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl UncheckedFrom<[u8; SIGNATURE_SERIALIZED_SIZE]> for Signature {
|
|
fn unchecked_from(data: [u8; SIGNATURE_SERIALIZED_SIZE]) -> Signature {
|
|
Signature(data)
|
|
}
|
|
}
|
|
|
|
impl Signature {
|
|
/// A new instance from the given 65-byte `data`.
|
|
///
|
|
/// NOTE: No checking goes on to ensure this is a real signature. Only use it if
|
|
/// you are certain that the array actually is a signature. GIGO!
|
|
pub fn from_raw(data: [u8; SIGNATURE_SERIALIZED_SIZE]) -> Signature {
|
|
Signature(data)
|
|
}
|
|
|
|
/// A new instance from the given slice that should be 65 bytes long.
|
|
///
|
|
/// NOTE: No checking goes on to ensure this is a real signature. Only use it if
|
|
/// you are certain that the array actually is a signature. GIGO!
|
|
pub fn from_slice(data: &[u8]) -> Option<Self> {
|
|
if data.len() != SIGNATURE_SERIALIZED_SIZE {
|
|
return None
|
|
}
|
|
let mut r = [0u8; SIGNATURE_SERIALIZED_SIZE];
|
|
r.copy_from_slice(data);
|
|
Some(Signature(r))
|
|
}
|
|
|
|
/// Recover the public key from this signature and a message.
|
|
#[cfg(feature = "full_crypto")]
|
|
pub fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Public> {
|
|
self.recover_prehashed(&blake2_256(message.as_ref()))
|
|
}
|
|
|
|
/// Recover the public key from this signature and a pre-hashed message.
|
|
#[cfg(feature = "full_crypto")]
|
|
pub fn recover_prehashed(&self, message: &[u8; 32]) -> Option<Public> {
|
|
let rid = RecoveryId::from_i32(self.0[64] as i32).ok()?;
|
|
let sig = RecoverableSignature::from_compact(&self.0[..64], rid).ok()?;
|
|
let message = Message::from_digest_slice(message).expect("Message is 32 bytes; qed");
|
|
|
|
#[cfg(feature = "std")]
|
|
let context = SECP256K1;
|
|
#[cfg(not(feature = "std"))]
|
|
let context = Secp256k1::verification_only();
|
|
|
|
context
|
|
.recover_ecdsa(&message, &sig)
|
|
.ok()
|
|
.map(|pubkey| Public(pubkey.serialize()))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "full_crypto")]
|
|
impl From<RecoverableSignature> for Signature {
|
|
fn from(recsig: RecoverableSignature) -> Signature {
|
|
let mut r = Self::default();
|
|
let (recid, sig) = recsig.serialize_compact();
|
|
r.0[..64].copy_from_slice(&sig);
|
|
// This is safe due to the limited range of possible valid ids.
|
|
r.0[64] = recid.to_i32() as u8;
|
|
r
|
|
}
|
|
}
|
|
|
|
/// Derive a single hard junction.
|
|
#[cfg(feature = "full_crypto")]
|
|
fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed {
|
|
("Secp256k1HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256)
|
|
}
|
|
|
|
/// A key pair.
|
|
#[cfg(feature = "full_crypto")]
|
|
#[derive(Clone)]
|
|
pub struct Pair {
|
|
public: Public,
|
|
secret: SecretKey,
|
|
}
|
|
|
|
#[cfg(feature = "full_crypto")]
|
|
impl TraitPair for Pair {
|
|
type Public = Public;
|
|
type Seed = Seed;
|
|
type Signature = Signature;
|
|
|
|
/// Make a new key pair from secret seed material. The slice must be 32 bytes long or it
|
|
/// will return `None`.
|
|
///
|
|
/// You should never need to use this; generate(), generate_with_phrase
|
|
fn from_seed_slice(seed_slice: &[u8]) -> Result<Pair, SecretStringError> {
|
|
let secret =
|
|
SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?;
|
|
|
|
#[cfg(feature = "std")]
|
|
let context = SECP256K1;
|
|
#[cfg(not(feature = "std"))]
|
|
let context = Secp256k1::signing_only();
|
|
|
|
let public = PublicKey::from_secret_key(&context, &secret);
|
|
let public = Public(public.serialize());
|
|
Ok(Pair { public, secret })
|
|
}
|
|
|
|
/// Derive a child key from a series of given junctions.
|
|
fn derive<Iter: Iterator<Item = DeriveJunction>>(
|
|
&self,
|
|
path: Iter,
|
|
_seed: Option<Seed>,
|
|
) -> Result<(Pair, Option<Seed>), DeriveError> {
|
|
let mut acc = self.seed();
|
|
for j in path {
|
|
match j {
|
|
DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath),
|
|
DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc),
|
|
}
|
|
}
|
|
Ok((Self::from_seed(&acc), Some(acc)))
|
|
}
|
|
|
|
/// Get the public key.
|
|
fn public(&self) -> Public {
|
|
self.public
|
|
}
|
|
|
|
/// Sign a message.
|
|
fn sign(&self, message: &[u8]) -> Signature {
|
|
self.sign_prehashed(&blake2_256(message))
|
|
}
|
|
|
|
/// Verify a signature on a message. Returns true if the signature is good.
|
|
fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, public: &Public) -> bool {
|
|
sig.recover(message).map(|actual| actual == *public).unwrap_or_default()
|
|
}
|
|
|
|
/// Return a vec filled with raw data.
|
|
fn to_raw_vec(&self) -> Vec<u8> {
|
|
self.seed().to_vec()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "full_crypto")]
|
|
impl Pair {
|
|
/// Get the seed for this key.
|
|
pub fn seed(&self) -> Seed {
|
|
self.secret.secret_bytes()
|
|
}
|
|
|
|
/// Exactly as `from_string` except that if no matches are found then, the the first 32
|
|
/// characters are taken (padded with spaces as necessary) and used as the MiniSecretKey.
|
|
#[cfg(feature = "std")]
|
|
pub fn from_legacy_string(s: &str, password_override: Option<&str>) -> Pair {
|
|
Self::from_string(s, password_override).unwrap_or_else(|_| {
|
|
let mut padded_seed: Seed = [b' '; 32];
|
|
let len = s.len().min(32);
|
|
padded_seed[..len].copy_from_slice(&s.as_bytes()[..len]);
|
|
Self::from_seed(&padded_seed)
|
|
})
|
|
}
|
|
|
|
/// Sign a pre-hashed message
|
|
pub fn sign_prehashed(&self, message: &[u8; 32]) -> Signature {
|
|
let message = Message::from_digest_slice(message).expect("Message is 32 bytes; qed");
|
|
|
|
#[cfg(feature = "std")]
|
|
let context = SECP256K1;
|
|
#[cfg(not(feature = "std"))]
|
|
let context = Secp256k1::signing_only();
|
|
|
|
context.sign_ecdsa_recoverable(&message, &self.secret).into()
|
|
}
|
|
|
|
/// Verify a signature on a pre-hashed message. Return `true` if the signature is valid
|
|
/// and thus matches the given `public` key.
|
|
pub fn verify_prehashed(sig: &Signature, message: &[u8; 32], public: &Public) -> bool {
|
|
match sig.recover_prehashed(message) {
|
|
Some(actual) => actual == *public,
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
/// Verify a signature on a message. Returns true if the signature is good.
|
|
/// Parses Signature using parse_overflowing_slice.
|
|
#[deprecated(note = "please use `verify` instead")]
|
|
pub fn verify_deprecated<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &Public) -> bool {
|
|
let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref()));
|
|
|
|
let parse_signature_overflowing = |x: [u8; SIGNATURE_SERIALIZED_SIZE]| {
|
|
let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64]).ok()?;
|
|
let rid = libsecp256k1::RecoveryId::parse(x[64]).ok()?;
|
|
Some((sig, rid))
|
|
};
|
|
|
|
let (sig, rid) = match parse_signature_overflowing(sig.0) {
|
|
Some(sigri) => sigri,
|
|
_ => return false,
|
|
};
|
|
match libsecp256k1::recover(&message, &sig, &rid) {
|
|
Ok(actual) => pubkey.0 == actual.serialize_compressed(),
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
// The `secp256k1` backend doesn't implement cleanup for their private keys.
|
|
// Currently we should take care of wiping the secret from memory.
|
|
// NOTE: this solution is not effective when `Pair` is moved around memory.
|
|
// The very same problem affects other cryptographic backends that are just using
|
|
// `zeroize`for their secrets.
|
|
#[cfg(feature = "full_crypto")]
|
|
impl Drop for Pair {
|
|
fn drop(&mut self) {
|
|
self.secret.non_secure_erase()
|
|
}
|
|
}
|
|
|
|
impl CryptoType for Public {
|
|
#[cfg(feature = "full_crypto")]
|
|
type Pair = Pair;
|
|
}
|
|
|
|
impl CryptoType for Signature {
|
|
#[cfg(feature = "full_crypto")]
|
|
type Pair = Pair;
|
|
}
|
|
|
|
#[cfg(feature = "full_crypto")]
|
|
impl CryptoType for Pair {
|
|
type Pair = Pair;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use crate::crypto::{
|
|
set_default_ss58_version, PublicError, Ss58AddressFormat, Ss58AddressFormatRegistry,
|
|
DEV_PHRASE,
|
|
};
|
|
use serde_json;
|
|
|
|
#[test]
|
|
fn default_phrase_should_be_used() {
|
|
assert_eq!(
|
|
Pair::from_string("//Alice///password", None).unwrap().public(),
|
|
Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password"))
|
|
.unwrap()
|
|
.public(),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn seed_and_derive_should_work() {
|
|
let seed = array_bytes::hex2array_unchecked(
|
|
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
|
|
);
|
|
let pair = Pair::from_seed(&seed);
|
|
assert_eq!(pair.seed(), seed);
|
|
let path = vec![DeriveJunction::Hard([0u8; 32])];
|
|
let derived = pair.derive(path.into_iter(), None).ok().unwrap();
|
|
assert_eq!(
|
|
derived.0.seed(),
|
|
array_bytes::hex2array_unchecked::<_, 32>(
|
|
"b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61"
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vector_should_work() {
|
|
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
|
|
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
|
|
));
|
|
let public = pair.public();
|
|
assert_eq!(
|
|
public,
|
|
Public::from_full(
|
|
&array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"),
|
|
).unwrap(),
|
|
);
|
|
let message = b"";
|
|
let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00");
|
|
let signature = Signature::from_raw(signature);
|
|
assert!(pair.sign(&message[..]) == signature);
|
|
assert!(Pair::verify(&signature, &message[..], &public));
|
|
}
|
|
|
|
#[test]
|
|
fn test_vector_by_string_should_work() {
|
|
let pair = Pair::from_string(
|
|
"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let public = pair.public();
|
|
assert_eq!(
|
|
public,
|
|
Public::from_full(
|
|
&array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"),
|
|
).unwrap(),
|
|
);
|
|
let message = b"";
|
|
let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00");
|
|
let signature = Signature::from_raw(signature);
|
|
assert!(pair.sign(&message[..]) == signature);
|
|
assert!(Pair::verify(&signature, &message[..], &public));
|
|
}
|
|
|
|
#[test]
|
|
fn generated_pair_should_work() {
|
|
let (pair, _) = Pair::generate();
|
|
let public = pair.public();
|
|
let message = b"Something important";
|
|
let signature = pair.sign(&message[..]);
|
|
assert!(Pair::verify(&signature, &message[..], &public));
|
|
assert!(!Pair::verify(&signature, b"Something else", &public));
|
|
}
|
|
|
|
#[test]
|
|
fn seeded_pair_should_work() {
|
|
let pair = Pair::from_seed(b"12345678901234567890123456789012");
|
|
let public = pair.public();
|
|
assert_eq!(
|
|
public,
|
|
Public::from_full(
|
|
&array_bytes::hex2bytes_unchecked("5676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba995840f3de562156558efbfdac3f16af0065e5f66795f4dd8262a228ef8c6d813"),
|
|
).unwrap(),
|
|
);
|
|
let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000");
|
|
let signature = pair.sign(&message[..]);
|
|
println!("Correct signature: {:?}", signature);
|
|
assert!(Pair::verify(&signature, &message[..], &public));
|
|
assert!(!Pair::verify(&signature, "Other message", &public));
|
|
}
|
|
|
|
#[test]
|
|
fn generate_with_phrase_recovery_possible() {
|
|
let (pair1, phrase, _) = Pair::generate_with_phrase(None);
|
|
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
|
|
|
|
assert_eq!(pair1.public(), pair2.public());
|
|
}
|
|
|
|
#[test]
|
|
fn generate_with_password_phrase_recovery_possible() {
|
|
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
|
|
let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap();
|
|
|
|
assert_eq!(pair1.public(), pair2.public());
|
|
}
|
|
|
|
#[test]
|
|
fn password_does_something() {
|
|
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
|
|
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
|
|
|
|
assert_ne!(pair1.public(), pair2.public());
|
|
}
|
|
|
|
#[test]
|
|
fn ss58check_roundtrip_works() {
|
|
let pair = Pair::from_seed(b"12345678901234567890123456789012");
|
|
let public = pair.public();
|
|
let s = public.to_ss58check();
|
|
println!("Correct: {}", s);
|
|
let cmp = Public::from_ss58check(&s).unwrap();
|
|
assert_eq!(cmp, public);
|
|
}
|
|
|
|
#[test]
|
|
fn ss58check_format_check_works() {
|
|
let pair = Pair::from_seed(b"12345678901234567890123456789012");
|
|
let public = pair.public();
|
|
let format = Ss58AddressFormatRegistry::Reserved46Account.into();
|
|
let s = public.to_ss58check_with_version(format);
|
|
assert_eq!(Public::from_ss58check_with_version(&s), Err(PublicError::FormatNotAllowed));
|
|
}
|
|
|
|
#[test]
|
|
fn ss58check_full_roundtrip_works() {
|
|
let pair = Pair::from_seed(b"12345678901234567890123456789012");
|
|
let public = pair.public();
|
|
let format = Ss58AddressFormatRegistry::PolkadotAccount.into();
|
|
let s = public.to_ss58check_with_version(format);
|
|
let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
|
|
assert_eq!(k, public);
|
|
assert_eq!(f, format);
|
|
|
|
let format = Ss58AddressFormat::custom(64);
|
|
let s = public.to_ss58check_with_version(format);
|
|
let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
|
|
assert_eq!(k, public);
|
|
assert_eq!(f, format);
|
|
}
|
|
|
|
#[test]
|
|
fn ss58check_custom_format_works() {
|
|
// We need to run this test in its own process to not interfere with other tests running in
|
|
// parallel and also relying on the ss58 version.
|
|
if std::env::var("RUN_CUSTOM_FORMAT_TEST") == Ok("1".into()) {
|
|
use crate::crypto::Ss58AddressFormat;
|
|
// temp save default format version
|
|
let default_format = crate::crypto::default_ss58_version();
|
|
// set current ss58 version is custom "200" `Ss58AddressFormat::Custom(200)`
|
|
|
|
set_default_ss58_version(Ss58AddressFormat::custom(200));
|
|
// custom addr encoded by version 200
|
|
let addr = "4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV";
|
|
Public::from_ss58check(addr).unwrap();
|
|
|
|
set_default_ss58_version(default_format);
|
|
// set current ss58 version to default version
|
|
let addr = "KWAfgC2aRG5UVD6CpbPQXCx4YZZUhvWqqAJE6qcYc9Rtr6g5C";
|
|
Public::from_ss58check(addr).unwrap();
|
|
|
|
println!("CUSTOM_FORMAT_SUCCESSFUL");
|
|
} else {
|
|
let executable = std::env::current_exe().unwrap();
|
|
let output = std::process::Command::new(executable)
|
|
.env("RUN_CUSTOM_FORMAT_TEST", "1")
|
|
.args(&["--nocapture", "ss58check_custom_format_works"])
|
|
.output()
|
|
.unwrap();
|
|
|
|
let output = String::from_utf8(output.stdout).unwrap();
|
|
assert!(output.contains("CUSTOM_FORMAT_SUCCESSFUL"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn signature_serialization_works() {
|
|
let pair = Pair::from_seed(b"12345678901234567890123456789012");
|
|
let message = b"Something important";
|
|
let signature = pair.sign(&message[..]);
|
|
let serialized_signature = serde_json::to_string(&signature).unwrap();
|
|
// Signature is 65 bytes, so 130 chars + 2 quote chars
|
|
assert_eq!(serialized_signature.len(), SIGNATURE_SERIALIZED_SIZE * 2 + 2);
|
|
let signature = serde_json::from_str(&serialized_signature).unwrap();
|
|
assert!(Pair::verify(&signature, &message[..], &pair.public()));
|
|
}
|
|
|
|
#[test]
|
|
fn signature_serialization_doesnt_panic() {
|
|
fn deserialize_signature(text: &str) -> Result<Signature, serde_json::error::Error> {
|
|
serde_json::from_str(text)
|
|
}
|
|
assert!(deserialize_signature("Not valid json.").is_err());
|
|
assert!(deserialize_signature("\"Not an actual signature.\"").is_err());
|
|
// Poorly-sized
|
|
assert!(deserialize_signature("\"abc123\"").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn sign_prehashed_works() {
|
|
let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
|
|
|
|
// `msg` shouldn't be mangled
|
|
let msg = [0u8; 32];
|
|
let sig1 = pair.sign_prehashed(&msg);
|
|
let sig2: Signature = {
|
|
let message = Message::from_digest_slice(&msg).unwrap();
|
|
SECP256K1.sign_ecdsa_recoverable(&message, &pair.secret).into()
|
|
};
|
|
assert_eq!(sig1, sig2);
|
|
|
|
// signature is actually different
|
|
let sig2 = pair.sign(&msg);
|
|
assert_ne!(sig1, sig2);
|
|
|
|
// using pre-hashed `msg` works
|
|
let msg = b"this should be hashed";
|
|
let sig1 = pair.sign_prehashed(&blake2_256(msg));
|
|
let sig2 = pair.sign(msg);
|
|
assert_eq!(sig1, sig2);
|
|
}
|
|
|
|
#[test]
|
|
fn verify_prehashed_works() {
|
|
let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
|
|
|
|
// `msg` and `sig` match
|
|
let msg = blake2_256(b"this should be hashed");
|
|
let sig = pair.sign_prehashed(&msg);
|
|
assert!(Pair::verify_prehashed(&sig, &msg, &pair.public()));
|
|
|
|
// `msg` and `sig` don't match
|
|
let msg = blake2_256(b"this is a different message");
|
|
assert!(!Pair::verify_prehashed(&sig, &msg, &pair.public()));
|
|
}
|
|
|
|
#[test]
|
|
fn recover_prehashed_works() {
|
|
let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
|
|
|
|
// recovered key matches signing key
|
|
let msg = blake2_256(b"this should be hashed");
|
|
let sig = pair.sign_prehashed(&msg);
|
|
let key = sig.recover_prehashed(&msg).unwrap();
|
|
assert_eq!(pair.public(), key);
|
|
|
|
// recovered key is useable
|
|
assert!(Pair::verify_prehashed(&sig, &msg, &key));
|
|
|
|
// recovered key and signing key don't match
|
|
let msg = blake2_256(b"this is a different message");
|
|
let key = sig.recover_prehashed(&msg).unwrap();
|
|
assert_ne!(pair.public(), key);
|
|
}
|
|
}
|