From f619b27fda087e58e01b8d34ea7c7c859684ebf4 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 15 Sep 2026 16:35:32 +0530 Subject: [PATCH 01/11] pkc%refac(ecdsa): tidy up `EcdsaError` --- pkgs/pkc/src/ecdsa/error.rs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/pkgs/pkc/src/ecdsa/error.rs b/pkgs/pkc/src/ecdsa/error.rs index 4b2005c8..6851e5be 100644 --- a/pkgs/pkc/src/ecdsa/error.rs +++ b/pkgs/pkc/src/ecdsa/error.rs @@ -32,28 +32,14 @@ pub enum EcdsaError { impl fmt::Display for EcdsaError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::InvalidPublicKey => { - write!(f, "public key bytes are not a valid curve point") - } - Self::InvalidRecoveryId => { - write!(f, "recovery id out of range (must be 0..4)") - } - Self::InvalidSecretKey => { - write!(f, "secret key bytes are not a valid scalar") - } - Self::InvalidSignature => { - write!(f, "signature bytes are malformed") - } - Self::MalformedDer => { - write!(f, "DER-encoded private key has invalid structure") - } - Self::RecoveryFailed => { - write!(f, "recovery failed; no valid public key") - } + Self::InvalidPublicKey => write!(f, "public key bytes are not a valid curve point"), + Self::InvalidRecoveryId => write!(f, "recovery id out of range (must be 0..4)"), + Self::InvalidSecretKey => write!(f, "secret key bytes are not a valid scalar"), + Self::InvalidSignature => write!(f, "signature bytes are malformed"), + Self::MalformedDer => write!(f, "DER-encoded private key has invalid structure"), + Self::RecoveryFailed => write!(f, "recovery failed; no valid public key"), Self::SigningFailed => write!(f, "signing failed"), - Self::VerifyFailed => { - write!(f, "signature verification failed") - } + Self::VerifyFailed => write!(f, "signature verification failed"), } } } From 91198b0fcca4b9d470f85e43b42ac727de35db69 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 14 Sep 2026 12:01:47 +0530 Subject: [PATCH 02/11] pkc%refac(ecdsa): drop the compact sign and recover convenience wrappers --- pkgs/pkc/src/ecdsa/public_ops.rs | 22 ++++++---------------- pkgs/pkc/src/ecdsa/secret_ops.rs | 12 +----------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/pkgs/pkc/src/ecdsa/public_ops.rs b/pkgs/pkc/src/ecdsa/public_ops.rs index b74a4de9..6bc36d2d 100644 --- a/pkgs/pkc/src/ecdsa/public_ops.rs +++ b/pkgs/pkc/src/ecdsa/public_ops.rs @@ -10,7 +10,7 @@ use super::error::EcdsaError; use super::public_bytes::{EcdsaPkBytes, Sec1Byte, ECDSA_PK_LEN}; use super::sig_ops::EcdsaSignature; use super::sig_rec_ops::EcdsaRecSignature; -use super::{Compression, EcdsaRecSigBytes, PubKeyHash}; +use super::{Compression, PubKeyHash}; use dash_types::type_id::{TypeId, Unencodable}; use dash_types::{dlgt_codec, type_cvrt}; @@ -162,18 +162,6 @@ impl EcdsaPublicKey { .map_err(|_| EcdsaError::RecoveryFailed) } - /// Recover a public key from a compact recoverable signature. - /// - /// # Errors - /// - /// Returns [`EcdsaError::InvalidSignature`] when the bag's scalars are not a - /// well-formed signature, plus every error listed for - /// [`recover`](Self::recover). - pub fn recover_compact(msg_hash: &[u8; 32], sig: &EcdsaRecSigBytes) -> Result { - let parsed = EcdsaRecSignature::try_from(*sig)?; - Self::recover(msg_hash, &parsed) - } - /// Verify a signature over a 32-byte prehashed message. /// /// Accepts anything that can view itself as a plain signature, so a @@ -251,7 +239,8 @@ mod tests { for v in corpus.vectors::("recover") { let sig = EcdsaSigBytes::from(arr_from_hex::<64>(&v.sig)); let compact = EcdsaRecSigBytes::from_parts(sig, v.recovery_id, Compression::Compressed).unwrap(); - let pk = EcdsaPublicKey::recover_compact(&arr_from_hex::<32>(&v.msg), &compact).unwrap(); + let parsed = EcdsaRecSignature::try_from(compact).unwrap(); + let pk = EcdsaPublicKey::recover(&arr_from_hex::<32>(&v.msg), &parsed).unwrap(); assert_eq!(pk.to_compressed(), arr_from_hex::<33>(&v.pk)); } } @@ -331,8 +320,9 @@ mod tests { #[rstest] fn recover_roundtrip(alice_pk: EcdsaPublicKey, alice_sk: EcdsaSecretKey, alice_rec_sig: EcdsaRecSignature) { - let compact_sig = alice_sk.sign_compact(&MSG).unwrap(); - assert_eq!(EcdsaPublicKey::recover_compact(&MSG, &compact_sig).unwrap(), alice_pk); + let compact_sig = EcdsaRecSigBytes::from(alice_sk.sign_recoverable(&MSG).unwrap()); + let restored = EcdsaRecSignature::try_from(compact_sig).unwrap(); + assert_eq!(EcdsaPublicKey::recover(&MSG, &restored).unwrap(), alice_pk); assert_eq!(EcdsaPublicKey::recover(&MSG, &alice_rec_sig).unwrap(), alice_pk); } diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index cca3142d..c6282c04 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -11,7 +11,7 @@ use super::public_ops::EcdsaPublicKey; use super::secret_bytes::{EcdsaSkBytes, ECDSA_SK_LEN}; use super::sig_ops::EcdsaSignature; use super::sig_rec_ops::EcdsaRecSignature; -use super::{Compression, EcdsaRecSigBytes}; +use super::Compression; use bitcoin_hashes::sha256d; use dash_num::Hash256; @@ -229,16 +229,6 @@ impl EcdsaSecretKey { .map_err(|_| EcdsaError::SigningFailed) } - /// Sign and return the compact recoverable signature bytes. - /// - /// # Errors - /// - /// Returns [`EcdsaError::SigningFailed`] if the underlying library rejects - /// the prehash. - pub fn sign_compact(&self, msg_hash: &[u8; 32]) -> Result { - Ok(self.sign_recoverable(msg_hash)?.into()) - } - /// Sign and return a recoverable signature (RFC 6979, low-S normalised). /// Recovery embeds the key's compression flag in the signature. /// From d02c7cc5edbbaa6172cbd266afbc9a929cc6144a Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 15 Sep 2026 16:15:29 +0530 Subject: [PATCH 03/11] pkc%lint(codeql): document curve-specific API divergences --- maint/codeql/rust/lib/pkc.qll | 17 +++++++ maint/codeql/rust/pkc.model.yml | 76 ++++++++++++++++++++++++++++++ maint/codeql/rust/pkc.ql | 82 +++++++++++++++++++++++++++++++++ maint/codeql/rust/qlpack.yml | 2 + 4 files changed, 177 insertions(+) create mode 100644 maint/codeql/rust/lib/pkc.qll create mode 100644 maint/codeql/rust/pkc.model.yml create mode 100644 maint/codeql/rust/pkc.ql diff --git a/maint/codeql/rust/lib/pkc.qll b/maint/codeql/rust/lib/pkc.qll new file mode 100644 index 00000000..26dd3989 --- /dev/null +++ b/maint/codeql/rust/lib/pkc.qll @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + * + * @description Rules for dash-pkc. + */ + +/** + * Holds if `name` belongs to `arm` alone for `role`, exempting the other arms + * from offering it. Rows live in `pkc.model.yml`. + * + * The rows name what an arm lacks rather than what the arms share, so a + * method added to one arm and forgotten in another is reported with no list + * to maintain. + */ +extensible predicate armOnly(string arm, string role, string name); diff --git a/maint/codeql/rust/pkc.model.yml b/maint/codeql/rust/pkc.model.yml new file mode 100644 index 00000000..bf9f7e55 --- /dev/null +++ b/maint/codeql/rust/pkc.model.yml @@ -0,0 +1,76 @@ +# List of known asymmetries in the APIs exposed for curves offered by dash-pkc +extensions: + - addsTo: + pack: base-sdk/codeql-rust + extensible: armOnly + data: + # BLS + # Aggregation + - ["Bls", "SecretKey", "aggregate"] # Add secret keys + - ["Bls", "PublicKey", "aggregate"] # Add + - ["Bls", "PublicKey", "secure_aggregate"] # Add w/ binding against rogue keys + - ["Bls", "Signature", "aggregate"] # Add + - ["Bls", "Signature", "secure_aggregate"] # Add w/ binding against rogue keys + - ["Bls", "Signature", "sub_insecure"] # Subtract + - ["Bls", "Signature", "verify_aggregates"] # Verify multi-message, multiple signers + - ["Bls", "Signature", "fast_verify_aggregates"] # Verify single-message, multiple signers + - ["Bls", "Signature", "secure_verify_aggregates"] # Verify w/ binding against rogue keys + # Integrated Encryption Scheme + - ["Bls", "SecretKey", "dh_exchange"] # Derive shared secret + - ["Bls", "SecretKey", "ies_decrypt"] # Decrypt blob + - ["Bls", "SecretKey", "ies_decrypt_multi"] # Decrypt multi + - ["Bls", "PublicKey", "ies_encrypt"] # Encrypt blob + - ["Bls", "PublicKey", "ies_encrypt_multi"] # Encrypt multi + # Key derivation + - ["Bls", "SecretKey", "from_ikm"] # Run key material through a KDF + # Misc + - ["Bls", "PublicKey", "to_bytes"] + - ["Bls", "Signature", "from_bytes"] + - ["Bls", "Signature", "to_bytes"] + # Scheme transform + - ["Bls", "SecretKey", "to_scheme"] + - ["Bls", "PublicKey", "to_scheme"] + - ["Bls", "Signature", "to_scheme"] + - ["Bls", "SecretKey", "sign_with"] # Specify non-type-native scheme + - ["Bls", "PublicKey", "verify_with"] # Specify non-type-native scheme + # Proof of possession + - ["Bls", "SecretKey", "prove_possession"] # Prove + - ["Bls", "PublicKey", "verify_possession"] # Verify + # Threshold signatures + - ["Bls", "SecretKey", "split"] # Issue shares + - ["Bls", "SecretKey", "derive_share"] # Get share from master keys + - ["Bls", "PublicKey", "derive_share"] # Get share from master keys + - ["Bls", "Signature", "recover_shares"] # Get signature + - ["Bls", "PublicKey", "recover_shares"] # Get master public key + # Tweaking + - ["Bls", "SecretKey", "add_tweak"] + - ["Bls", "PublicKey", "add_tweak"] + - ["Bls", "PublicKey", "mul_tweak"] + # ECDSA + # Compression + - ["Ecdsa", "SecretKey", "is_compressed"] # Form the derived key will take + - ["Ecdsa", "PublicKey", "is_compressed"] # Query + - ["Ecdsa", "PublicKey", "is_hybrid"] # Query legacy parity hint + - ["Ecdsa", "PublicKey", "decompress"] # Switch to uncompressed + - ["Ecdsa", "PublicKey", "to_compressed"] # Emit 33-byte SEC1 + - ["Ecdsa", "PublicKey", "to_uncompressed"] # Emit 65-byte SEC1 + - ["Ecdsa", "PublicKey", "to_hybrid"] # Emit 65-byte SEC1 w/ parity hint + - ["Ecdsa", "SkBytes", "is_compressed"] # Query + - ["Ecdsa", "PkBytes", "is_compressed"] # Query + - ["Ecdsa", "PkBytes", "size"] # Live length, since SEC1 is not fixed width + # Encoding + - ["Ecdsa", "Signature", "from_der"] # Read DER + - ["Ecdsa", "Signature", "to_der"] # Emit DER + - ["Ecdsa", "PkHash", "to_base58c"] # Emit Base58c address + - ["Ecdsa", "SkBytes", "from_wif"] # Read Base58c wallet import format + - ["Ecdsa", "SkBytes", "to_wif"] # Emit Base58c wallet import format + - ["Ecdsa", "Signature", "from_compact"] # Read 64-byte r||s + - ["Ecdsa", "Signature", "to_compact"] # Emit 64-byte r||s + # Malleability + - ["Ecdsa", "Signature", "is_low_s"] # Query whether S is in the lower half + - ["Ecdsa", "Signature", "normalize_s"] # Move S into the lower half + # Misc. + - ["Ecdsa", "SecretKey", "negate"] # Flips the scalar + # Recoverable signatures + - ["Ecdsa", "SecretKey", "sign_recoverable"] # Sign + - ["Ecdsa", "PublicKey", "recover"] # Get public key from a signature diff --git a/maint/codeql/rust/pkc.ql b/maint/codeql/rust/pkc.ql new file mode 100644 index 00000000..0740f954 --- /dev/null +++ b/maint/codeql/rust/pkc.ql @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + * + * @id base-sdk/pkc-rules + * @name Rules for dash-pkc + * @description The arms must offer the same operations under the same names. + * @kind problem + * @precision high + * @problem.severity warning + * @tags maintainability + */ + +import lib.filters +import lib.fmt +import lib.policy +import lib.pkc +import lib.traits +import rust + +/** + * Holds if `t` is `arm`'s type for `role`, split off the name rather than the + * module so that every arm naming a role holds the same one. A role only one + * arm holds never pairs, and reports nothing. + */ +predicate armRole(TypeItem t, string arm, string role) { + isSourceType(t) and + isEnforcedCrate(fileOf(t)) and + exists(string name | + name = t.getName().getText() and + arm = name.regexpCapture("^(Bls|Ecdsa|Eddsa)([A-Z].*)$", 1) and + role = name.regexpCapture("^(Bls|Ecdsa|Eddsa)([A-Z].*)$", 2) + ) +} + +/** Holds if `f` is `pub`, rather than restricted to a scope. */ +predicate isBarePub(Function f) { + exists(f.getVisibility()) and + not exists(f.getVisibility().getPath()) +} + +/** + * Holds if `t` offers `name` as a public inherent method. + * + * Matched wherever the impl sits, not through `inherentImpl`, which the + * declaration order rule needs to be file-local; an arm spreads a type's + * methods over several modules. + * + * Macro-written impls are skipped, since what a macro grants a type follows + * from which macro it expands rather than from the arm. + */ +predicate publicMethod(TypeItem t, string name) { + exists(Impl i, Function f | + not exists(MacroItems m | i = m.getItem(_)) and + implSelfName(i) = t.getName().getText() and + not exists(implTraitName(i)) and + isEnforcedCrate(fileOf(i)) and + f = i.getAssocItemList().getAnAssocItem() and + isBarePub(f) and + not isTestCode(f) and + name = f.getName().getText() + ) +} + +/** + * Holds if `lacks` is missing `name`, which `arm` offers for the same role. + */ +predicate shapeGap(TypeItem lacks, string role, string name, string arm) { + exists(TypeItem offers, string lacking | + armRole(offers, arm, role) and + armRole(lacks, lacking, role) and + lacking != arm and + publicMethod(offers, name) and + not publicMethod(lacks, name) and + not armOnly(arm, role, name) + ) +} + +from TypeItem t, string role, string name, string arm +where shapeGap(t, role, name, arm) +select t, fmt("{0} offers {1}, {2} does not", arm + role, fmt("{0}()", name), t.getName().getText()) diff --git a/maint/codeql/rust/qlpack.yml b/maint/codeql/rust/qlpack.yml index 842cef04..7aa0a6db 100644 --- a/maint/codeql/rust/qlpack.yml +++ b/maint/codeql/rust/qlpack.yml @@ -1,5 +1,7 @@ name: base-sdk/codeql-rust version: 0.0.0 +dataExtensions: + - "**/*.model.yml" dependencies: codeql/rust-all: ~0.2.17 codeql/rust-queries: ~0.1.38 From 7fd619724eb06e7576f25159303db9a5341dbf57 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 11 Sep 2026 19:35:08 +0530 Subject: [PATCH 04/11] pkc%refac(ecdsa): consolidate curve constants --- pkgs/pkc/src/ecdsa/curve_consts.rs | 24 ++++++++++++++++++++++++ pkgs/pkc/src/ecdsa/mod.rs | 1 + pkgs/pkc/src/ecdsa/secret_ops.rs | 17 ++++------------- pkgs/pkc/src/ecdsa/tests.rs | 2 +- 4 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 pkgs/pkc/src/ecdsa/curve_consts.rs diff --git a/pkgs/pkc/src/ecdsa/curve_consts.rs b/pkgs/pkc/src/ecdsa/curve_consts.rs new file mode 100644 index 00000000..e23c0730 --- /dev/null +++ b/pkgs/pkc/src/ecdsa/curve_consts.rs @@ -0,0 +1,24 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! secp256k1 constants. + +use super::secret_bytes::ECDSA_SK_LEN; + +use hex_conservative::hex; + +/// DER lengths of a private key with a compressed and an uncompressed public +/// key respectively. +pub(super) const DER_SIZES: &[usize] = &[214, 279]; + +/// ASN.1 object identifier for a prime-field curve. +pub(super) const OID_PRIME_FIELD: &[u8] = &hex!("2a8648ce3d0101"); + +/// The field prime. +pub(super) const PRIME: &[u8; ECDSA_SK_LEN] = &hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + +/// The group order. +pub(super) const ORDER: &[u8; ECDSA_SK_LEN] = &hex!("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); diff --git a/pkgs/pkc/src/ecdsa/mod.rs b/pkgs/pkc/src/ecdsa/mod.rs index 0caddf92..c81854f2 100644 --- a/pkgs/pkc/src/ecdsa/mod.rs +++ b/pkgs/pkc/src/ecdsa/mod.rs @@ -51,6 +51,7 @@ impl From for Compression { cfg_if::cfg_if! { if #[cfg(feature = "ecdsa")] { + mod curve_consts; mod public_ops; mod secret_ops; mod sig_ops; diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index c6282c04..4def1d9c 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -6,6 +6,7 @@ //! secp256k1 secret key. +use super::curve_consts::{DER_SIZES, OID_PRIME_FIELD, ORDER, PRIME}; use super::error::EcdsaError; use super::public_ops::EcdsaPublicKey; use super::secret_bytes::{EcdsaSkBytes, ECDSA_SK_LEN}; @@ -18,7 +19,6 @@ use dash_num::Hash256; use dash_types::codec::{ensure, BaseCodec, DecodeError, EncodeBuf, Hashable}; use dash_types::type_id::TypeId; use dash_types::{impl_stype, type_cvrt, ArrayBuf, Numeric}; -use hex_conservative::hex; use k256::ecdsa::{signature::hazmat::PrehashSigner, SigningKey}; use k256::elliptic_curve::ops::Neg; use k256::elliptic_curve::Generate; @@ -28,16 +28,6 @@ use zeroize::{Zeroize, Zeroizing}; use core::fmt; -/// DER lengths of a private key with a compressed and an uncompressed public -/// key respectively. -const DER_SIZES: &[usize] = &[214, 279]; -/// ASN.1 object identifier for a prime-field curve. -const OID_PRIME_FIELD: &[u8] = &hex!("2a8648ce3d0101"); -/// secp256k1 field prime. -const PRIME: &[u8; ECDSA_SK_LEN] = &hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); -/// secp256k1 group order. -pub(super) const ORDER: &[u8; ECDSA_SK_LEN] = &hex!("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); - /// Emit a DER header followed by `bytes`. fn der_bytes(buf: &mut impl EncodeBuf, tag: u8, bytes: &[u8]) { der_header(buf, tag, bytes.len()); @@ -280,6 +270,7 @@ type_cvrt!(TryFrom for EcdsaSecretKey, EcdsaError, |bytes| { #[cfg(test)] #[expect(clippy::ptr_arg, clippy::unwrap_used, reason = "test code")] mod tests { + use super::OID_PRIME_FIELD; use crate::ecdsa::tests::*; use crate::ecdsa::{Compression, EcdsaPublicKey, EcdsaSecretKey}; use crate::prelude::*; @@ -345,8 +336,8 @@ mod tests { fn corrupt_tampered_curve_oid(buf: &mut Vec, _alice: &EcdsaSecretKey, _bob: &EcdsaSecretKey) { let pos = buf - .windows(super::OID_PRIME_FIELD.len()) - .position(|w| w == super::OID_PRIME_FIELD) + .windows(OID_PRIME_FIELD.len()) + .position(|w| w == OID_PRIME_FIELD) .unwrap(); buf[pos] ^= 0xff; } diff --git a/pkgs/pkc/src/ecdsa/tests.rs b/pkgs/pkc/src/ecdsa/tests.rs index ec5206f0..3b8958f1 100644 --- a/pkgs/pkc/src/ecdsa/tests.rs +++ b/pkgs/pkc/src/ecdsa/tests.rs @@ -6,7 +6,7 @@ //! Common test definitions. -use super::secret_ops::ORDER; +use super::curve_consts::ORDER; use crate::ecdsa::{Compression, EcdsaPublicKey, EcdsaRecSignature, EcdsaSecretKey, EcdsaSignature}; use hex_conservative::hex; From 12f18761f162789c56bf1d8ebcdfb16be0f9d300 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:02:20 +0530 Subject: [PATCH 05/11] pkc%refac(codec): make `codec` optional for `ecdsa`-restricted builds --- pkgs/pkc/Cargo.toml | 2 +- pkgs/pkc/src/ecdsa/mod.rs | 5 ++++- pkgs/pkc/src/ecdsa/public_bytes.rs | 13 +++++++++---- pkgs/pkc/src/ecdsa/public_hash.rs | 5 +++++ pkgs/pkc/src/ecdsa/public_ops.rs | 18 +++++++++++++----- pkgs/pkc/src/ecdsa/secret_bytes.rs | 4 ++++ pkgs/pkc/src/ecdsa/secret_ops.rs | 23 +++++++++++++++++++---- pkgs/pkc/src/ecdsa/sig_bytes.rs | 15 +++++++++++---- pkgs/pkc/src/ecdsa/sig_ops.rs | 13 ++++++++++--- pkgs/pkc/src/ecdsa/sig_rec_bytes.rs | 15 +++++++++++---- pkgs/pkc/src/ecdsa/sig_rec_ops.rs | 10 +++++++--- pkgs/pkc/src/lib.rs | 14 +++++--------- 12 files changed, 99 insertions(+), 38 deletions(-) diff --git a/pkgs/pkc/Cargo.toml b/pkgs/pkc/Cargo.toml index c4b8b681..ca5c31e8 100644 --- a/pkgs/pkc/Cargo.toml +++ b/pkgs/pkc/Cargo.toml @@ -65,7 +65,7 @@ codec = [ "dash-num/codec", "dash-types/codec", ] -ecdsa = ["codec", "dep:k256", "dep:rand_core"] +ecdsa = ["dep:k256", "dep:rand_core"] serde = ["codec", "dep:serde", "dash-num/serde", "dash-types/serde"] full = ["bls", "codec", "ecdsa", "serde", "std", "tests"] tests = ["std", "dep:rstest"] diff --git a/pkgs/pkc/src/ecdsa/mod.rs b/pkgs/pkc/src/ecdsa/mod.rs index c81854f2..83ebd70d 100644 --- a/pkgs/pkc/src/ecdsa/mod.rs +++ b/pkgs/pkc/src/ecdsa/mod.rs @@ -13,6 +13,7 @@ mod secret_bytes; mod sig_bytes; mod sig_rec_bytes; +#[cfg(feature = "codec")] use dash_types::type_id::Unencodable; pub use error::EcdsaError; @@ -24,7 +25,8 @@ pub use sig_rec_bytes::EcdsaRecSigBytes; /// Whether a key's public counterpart serializes in compressed (33-byte) or /// uncompressed (65-byte) SEC1 form. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Unencodable)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "codec", derive(Unencodable))] pub enum Compression { /// The public key serializes compressed. Compressed, @@ -51,6 +53,7 @@ impl From for Compression { cfg_if::cfg_if! { if #[cfg(feature = "ecdsa")] { + #[allow(dead_code, reason = "curve constants")] mod curve_consts; mod public_ops; mod secret_ops; diff --git a/pkgs/pkc/src/ecdsa/public_bytes.rs b/pkgs/pkc/src/ecdsa/public_bytes.rs index baee658e..bbd3f8a6 100644 --- a/pkgs/pkc/src/ecdsa/public_bytes.rs +++ b/pkgs/pkc/src/ecdsa/public_bytes.rs @@ -11,9 +11,11 @@ use crate::prelude::*; use bitcoin_hashes::{ripemd160, sha256}; use cfg_if::cfg_if; -use dash_types::codec::{read_bytes, BaseCodec, DecodeError, EncodeBuf, Hashable}; -use dash_types::type_id::TypeId; -use dash_types::{enum_map, impl_type, CompactSize}; +#[cfg(feature = "codec")] +use dash_types::codec::{read_bytes, BaseCodec, DecodeError, EncodeBuf}; +use dash_types::{enum_map, Hashable}; +#[cfg(feature = "codec")] +use dash_types::{impl_type, type_id::TypeId, CompactSize}; use core::cmp::Ordering; use core::fmt; @@ -61,12 +63,14 @@ impl Sec1Byte { /// The header byte is held as a parsed SEC1 prefix. The coordinates stay /// unvalidated: only [`EcdsaPublicKey`](crate::ecdsa::EcdsaPublicKey) checks /// curve membership. -#[derive(Clone, Copy, Eq, Hash, PartialEq, TypeId)] +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "codec", derive(TypeId))] pub struct EcdsaPkBytes { prefix: Sec1Byte, buf: [u8; ECDSA_PK_LEN + 1], } +#[cfg(feature = "codec")] impl BaseCodec for EcdsaPkBytes { fn decode(data: &mut &[u8]) -> Result { let n = CompactSize::decode(data)?.into_len(ECDSA_PK_LEN + 1)?; @@ -94,6 +98,7 @@ impl BaseCodec for EcdsaPkBytes { } } +#[cfg(feature = "codec")] impl_type!(EcdsaPkBytes); impl Hashable for EcdsaPkBytes { diff --git a/pkgs/pkc/src/ecdsa/public_hash.rs b/pkgs/pkc/src/ecdsa/public_hash.rs index 28a3c215..1d479fef 100644 --- a/pkgs/pkc/src/ecdsa/public_hash.rs +++ b/pkgs/pkc/src/ecdsa/public_hash.rs @@ -6,11 +6,15 @@ //! Hashed representation of secp256k1 public key. +#[cfg(feature = "codec")] use crate::prelude::*; +#[cfg(feature = "codec")] use base58ck::encode_check; use dash_num::make_hash; +#[cfg(feature = "codec")] use dash_types::codec::{BaseCodec, EncodeBuf}; +#[cfg(feature = "codec")] use dash_types::ArrayBuf; make_hash! { @@ -18,6 +22,7 @@ make_hash! { PubKeyHash, 20 } +#[cfg(feature = "codec")] impl PubKeyHash { /// Encode as a Base58Check address with the given version prefix. pub fn to_base58c(&self, prefix: u8) -> String { diff --git a/pkgs/pkc/src/ecdsa/public_ops.rs b/pkgs/pkc/src/ecdsa/public_ops.rs index 6bc36d2d..dce5a049 100644 --- a/pkgs/pkc/src/ecdsa/public_ops.rs +++ b/pkgs/pkc/src/ecdsa/public_ops.rs @@ -10,10 +10,15 @@ use super::error::EcdsaError; use super::public_bytes::{EcdsaPkBytes, Sec1Byte, ECDSA_PK_LEN}; use super::sig_ops::EcdsaSignature; use super::sig_rec_ops::EcdsaRecSignature; -use super::{Compression, PubKeyHash}; - +use super::Compression; +#[cfg(feature = "codec")] +use super::PubKeyHash; + +#[cfg(feature = "codec")] +use dash_types::dlgt_codec; +use dash_types::type_cvrt; +#[cfg(feature = "codec")] use dash_types::type_id::{TypeId, Unencodable}; -use dash_types::{dlgt_codec, type_cvrt}; use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey}; use core::hash::{Hash, Hasher}; @@ -23,7 +28,8 @@ use core::hash::{Hash, Hasher}; /// Retained separately from the curve point because the point alone cannot /// distinguish the uncompressed and hybrid encodings, and re-emitting one as /// the other would change the key's wire image and therefore its hash. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Unencodable)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "codec", derive(Unencodable))] pub(super) enum PkForm { /// 33-byte `0x02`/`0x03` form. Compressed, @@ -34,7 +40,8 @@ pub(super) enum PkForm { } /// A secp256k1 public key. -#[derive(Clone, Debug, Eq, PartialEq, TypeId)] +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "codec", derive(TypeId))] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr(feature = "serde", serde(into = "EcdsaPkBytes", try_from = "EcdsaPkBytes"))] pub struct EcdsaPublicKey { @@ -42,6 +49,7 @@ pub struct EcdsaPublicKey { form: PkForm, } +#[cfg(feature = "codec")] dlgt_codec!(EcdsaPublicKey => EcdsaPkBytes, PubKeyHash, EcdsaError, ECDSA_PK_LEN + 2); impl EcdsaPublicKey { diff --git a/pkgs/pkc/src/ecdsa/secret_bytes.rs b/pkgs/pkc/src/ecdsa/secret_bytes.rs index f1ebbc2e..f0595df9 100644 --- a/pkgs/pkc/src/ecdsa/secret_bytes.rs +++ b/pkgs/pkc/src/ecdsa/secret_bytes.rs @@ -7,8 +7,10 @@ //! secp256k1 secret key byte bag. use super::Compression; +#[cfg(feature = "codec")] use crate::prelude::*; +#[cfg(feature = "codec")] use base58ck::{decode_check, encode_check}; use dash_types::derive_sbytes; use subtle::ConstantTimeEq; @@ -48,6 +50,7 @@ impl EcdsaSkBytes { self.compressed } + #[cfg(feature = "codec")] /// Decode a wallet import format-encoded private key. /// /// Returns `None` on a bad checksum, an unexpected version prefix, a length @@ -75,6 +78,7 @@ impl EcdsaSkBytes { Zeroizing::new(self.inner) } + #[cfg(feature = "codec")] /// Encode as a wallet import format string. /// /// Returns `None` for the all-zero scalar, which [`from_wif`](Self::from_wif) diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index 4def1d9c..c6336633 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -6,6 +6,7 @@ //! secp256k1 secret key. +#[cfg(feature = "codec")] use super::curve_consts::{DER_SIZES, OID_PRIME_FIELD, ORDER, PRIME}; use super::error::EcdsaError; use super::public_ops::EcdsaPublicKey; @@ -14,26 +15,35 @@ use super::sig_ops::EcdsaSignature; use super::sig_rec_ops::EcdsaRecSignature; use super::Compression; +#[cfg(feature = "codec")] use bitcoin_hashes::sha256d; +#[cfg(feature = "codec")] use dash_num::Hash256; -use dash_types::codec::{ensure, BaseCodec, DecodeError, EncodeBuf, Hashable}; -use dash_types::type_id::TypeId; -use dash_types::{impl_stype, type_cvrt, ArrayBuf, Numeric}; +#[cfg(feature = "codec")] +use dash_types::codec::{ensure, BaseCodec, DecodeError, EncodeBuf}; +use dash_types::type_cvrt; +#[cfg(feature = "codec")] +use dash_types::{impl_stype, type_id::TypeId, ArrayBuf}; +#[cfg(feature = "codec")] +use dash_types::{Hashable, Numeric}; use k256::ecdsa::{signature::hazmat::PrehashSigner, SigningKey}; use k256::elliptic_curve::ops::Neg; use k256::elliptic_curve::Generate; +#[cfg(feature = "codec")] use k256::{elliptic_curve::sec1::ToSec1Point, AffinePoint}; use rand_core::CryptoRng; use zeroize::{Zeroize, Zeroizing}; use core::fmt; +#[cfg(feature = "codec")] /// Emit a DER header followed by `bytes`. fn der_bytes(buf: &mut impl EncodeBuf, tag: u8, bytes: &[u8]) { der_header(buf, tag, bytes.len()); buf.extend_from_slice(bytes); // nosemgrep: codec-no-raw-extend } +#[cfg(feature = "codec")] /// Emit a DER tag and its short, one-byte, or two-byte length. fn der_header(buf: &mut impl EncodeBuf, tag: u8, len: usize) { debug_assert!(len <= u16::MAX as usize, "der_header: length exceeds u16"); @@ -45,6 +55,7 @@ fn der_header(buf: &mut impl EncodeBuf, tag: u8, len: usize) { } } +#[cfg(feature = "codec")] /// Emit a DER INTEGER, prefixing a zero byte when the high bit is set. fn der_uint(buf: &mut impl EncodeBuf, bytes: &[u8]) { debug_assert!(!bytes.is_empty(), "der_uint: empty input"); @@ -56,12 +67,14 @@ fn der_uint(buf: &mut impl EncodeBuf, bytes: &[u8]) { } /// A secp256k1 secret key. -#[derive(Clone, TypeId)] +#[derive(Clone)] +#[cfg_attr(feature = "codec", derive(TypeId))] pub struct EcdsaSecretKey { inner: SigningKey, compressed: bool, } +#[cfg(feature = "codec")] impl BaseCodec for EcdsaSecretKey { fn decode(data: &mut &[u8]) -> Result> { ensure(data, 4).map_err(|e| e.lift())?; @@ -144,8 +157,10 @@ impl BaseCodec for EcdsaSecretKey { } } +#[cfg(feature = "codec")] impl_stype!(EcdsaSecretKey, DER_SIZES[1], EcdsaError); +#[cfg(feature = "codec")] impl Hashable for EcdsaSecretKey { type Hash = Hash256; diff --git a/pkgs/pkc/src/ecdsa/sig_bytes.rs b/pkgs/pkc/src/ecdsa/sig_bytes.rs index 01375315..d679bf64 100644 --- a/pkgs/pkc/src/ecdsa/sig_bytes.rs +++ b/pkgs/pkc/src/ecdsa/sig_bytes.rs @@ -6,14 +6,18 @@ //! secp256k1 signature byte bag. +#[cfg(feature = "codec")] use crate::prelude::*; use bitcoin_hashes::sha256d; use cfg_if::cfg_if; use dash_num::Hash256; -use dash_types::codec::{read_bytes, BaseCodec, DecodeError, EncodeBuf, Hashable}; -use dash_types::type_id::TypeId; -use dash_types::{impl_type, type_cvrt, CompactSize, Numeric}; +#[cfg(feature = "codec")] +use dash_types::codec::{read_bytes, BaseCodec, DecodeError, EncodeBuf}; +use dash_types::type_cvrt; +#[cfg(feature = "codec")] +use dash_types::{impl_type, type_id::TypeId, CompactSize}; +use dash_types::{Hashable, Numeric}; use core::fmt; @@ -21,9 +25,11 @@ use core::fmt; pub const ECDSA_SIG_LEN: usize = 64; /// Raw compact ECDSA signature bytes (r || s, unvalidated scalars). -#[derive(Clone, Copy, Eq, Hash, PartialEq, TypeId)] +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "codec", derive(TypeId))] pub struct EcdsaSigBytes([u8; ECDSA_SIG_LEN]); +#[cfg(feature = "codec")] impl BaseCodec for EcdsaSigBytes { fn decode(data: &mut &[u8]) -> Result { let n = CompactSize::decode(data)?.into_len(ECDSA_SIG_LEN)?; @@ -44,6 +50,7 @@ impl BaseCodec for EcdsaSigBytes { } } +#[cfg(feature = "codec")] impl_type!(EcdsaSigBytes); impl Hashable for EcdsaSigBytes { diff --git a/pkgs/pkc/src/ecdsa/sig_ops.rs b/pkgs/pkc/src/ecdsa/sig_ops.rs index f4e44560..f7de5fc6 100644 --- a/pkgs/pkc/src/ecdsa/sig_ops.rs +++ b/pkgs/pkc/src/ecdsa/sig_ops.rs @@ -10,16 +10,21 @@ use super::error::EcdsaError; use super::sig_bytes::ECDSA_SIG_LEN; use super::EcdsaSigBytes; +#[cfg(feature = "codec")] use dash_num::Hash256; +#[cfg(feature = "codec")] +use dash_types::dlgt_codec; +use dash_types::type_cvrt; +#[cfg(feature = "codec")] use dash_types::type_id::{TypeId, Unencodable}; -use dash_types::{dlgt_codec, type_cvrt}; use k256::ecdsa::{DerSignature, Signature}; use k256::elliptic_curve::scalar::IsHigh; use core::hash::{Hash, Hasher}; /// An ECDSA signature (64-byte compact r||s). -#[derive(Clone, Debug, Eq, PartialEq, TypeId)] +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "codec", derive(TypeId))] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( feature = "serde", @@ -27,6 +32,7 @@ use core::hash::{Hash, Hasher}; )] pub struct EcdsaSignature(Signature); +#[cfg(feature = "codec")] dlgt_codec!(EcdsaSignature => EcdsaSigBytes, Hash256, EcdsaError, ECDSA_SIG_LEN + 1); impl EcdsaSignature { @@ -101,7 +107,8 @@ impl AsRef for EcdsaSignature { } /// DER-encoded ECDSA signature (variable length, typically 70-72 bytes). -#[derive(Clone, Debug, Unencodable)] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "codec", derive(Unencodable))] pub struct EcdsaDerSig(DerSignature); impl EcdsaDerSig { diff --git a/pkgs/pkc/src/ecdsa/sig_rec_bytes.rs b/pkgs/pkc/src/ecdsa/sig_rec_bytes.rs index 6345f696..ffc56ba1 100644 --- a/pkgs/pkc/src/ecdsa/sig_rec_bytes.rs +++ b/pkgs/pkc/src/ecdsa/sig_rec_bytes.rs @@ -8,14 +8,18 @@ use super::sig_bytes::{EcdsaSigBytes, ECDSA_SIG_LEN}; use super::Compression; +#[cfg(feature = "codec")] use crate::prelude::*; use bitcoin_hashes::sha256d; use cfg_if::cfg_if; use dash_num::Hash256; -use dash_types::codec::{read_bytes, BaseCodec, DecodeError, EncodeBuf, Hashable}; -use dash_types::type_id::TypeId; -use dash_types::{enum_map, impl_type, type_cvrt, CompactSize, Numeric}; +#[cfg(feature = "codec")] +use dash_types::codec::{read_bytes, BaseCodec, DecodeError, EncodeBuf}; +use dash_types::{enum_map, type_cvrt}; +#[cfg(feature = "codec")] +use dash_types::{impl_type, type_id::TypeId, CompactSize}; +use dash_types::{Hashable, Numeric}; use core::fmt; @@ -82,12 +86,14 @@ impl CompactFlags { /// Compact recoverable ECDSA signature bytes: one header byte carrying the /// recovery id and compression flag, then `r || s`. -#[derive(Clone, Copy, Eq, Hash, PartialEq, TypeId)] +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "codec", derive(TypeId))] pub struct EcdsaRecSigBytes { flags: CompactFlags, sig: EcdsaSigBytes, } +#[cfg(feature = "codec")] impl BaseCodec for EcdsaRecSigBytes { fn decode(data: &mut &[u8]) -> Result { let n = CompactSize::decode(data)?.into_len(ECDSA_SIG_LEN + 1)?; @@ -121,6 +127,7 @@ impl BaseCodec for EcdsaRecSigBytes { } } +#[cfg(feature = "codec")] impl_type!(EcdsaRecSigBytes); impl Hashable for EcdsaRecSigBytes { diff --git a/pkgs/pkc/src/ecdsa/sig_rec_ops.rs b/pkgs/pkc/src/ecdsa/sig_rec_ops.rs index 3df33a7e..5f588879 100644 --- a/pkgs/pkc/src/ecdsa/sig_rec_ops.rs +++ b/pkgs/pkc/src/ecdsa/sig_rec_ops.rs @@ -12,13 +12,16 @@ use super::sig_ops::EcdsaSignature; use super::sig_rec_bytes::{CompactFlags, EcdsaRecSigBytes}; use super::Compression; +#[cfg(feature = "codec")] use dash_num::Hash256; -use dash_types::type_id::TypeId; -use dash_types::{dlgt_codec, type_cvrt}; +use dash_types::type_cvrt; +#[cfg(feature = "codec")] +use dash_types::{dlgt_codec, type_id::TypeId}; use k256::ecdsa::{RecoveryId, Signature}; /// An ECDSA signature with recovery id and compression metadata. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "codec", derive(TypeId))] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr(feature = "serde", serde(into = "EcdsaRecSigBytes", try_from = "EcdsaRecSigBytes"))] pub struct EcdsaRecSignature { @@ -26,6 +29,7 @@ pub struct EcdsaRecSignature { flags: CompactFlags, } +#[cfg(feature = "codec")] dlgt_codec!(EcdsaRecSignature => EcdsaRecSigBytes, Hash256, EcdsaError, ECDSA_SIG_LEN + 2); impl EcdsaRecSignature { diff --git a/pkgs/pkc/src/lib.rs b/pkgs/pkc/src/lib.rs index e929d2d8..dcc439ae 100644 --- a/pkgs/pkc/src/lib.rs +++ b/pkgs/pkc/src/lib.rs @@ -18,14 +18,10 @@ mod aes_cbc; mod prelude; pub mod bls; +pub mod ecdsa; -cfg_if::cfg_if! { - if #[cfg(feature = "codec")] { - pub mod ecdsa; - - #[doc(hidden)] - pub mod __private { - pub use crate::ecdsa::PubKeyHash as __PubKeyHash; - } - } +#[cfg(feature = "codec")] +#[doc(hidden)] +pub mod __private { + pub use crate::ecdsa::PubKeyHash as __PubKeyHash; } From 843b6081764dbd137b58e836c62f6341069da747 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:19:03 +0530 Subject: [PATCH 06/11] pkc%refac(ecdsa): rename `PubKeyHash` to `EcdsaPkHash` --- maint/codeql/rust/lib/imports.qll | 2 +- pkgs/pkc/src/ecdsa/mod.rs | 2 +- pkgs/pkc/src/ecdsa/public_bytes.rs | 4 ++-- pkgs/pkc/src/ecdsa/public_hash.rs | 4 ++-- pkgs/pkc/src/ecdsa/public_ops.rs | 4 ++-- pkgs/pkc/src/lib.rs | 2 +- pkgs/script/src/lib.rs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/maint/codeql/rust/lib/imports.qll b/maint/codeql/rust/lib/imports.qll index 671661c7..0aa7365e 100644 --- a/maint/codeql/rust/lib/imports.qll +++ b/maint/codeql/rust/lib/imports.qll @@ -78,7 +78,7 @@ private predicate isAllowlistedReexport(Use u) { ( // Workaround for the orphan rule, not part of public API usePrefix(u) = "dash_pkc" and - u.getUseTree().getPath().getSegment().getIdentifier().getText() = "__PubKeyHash" + u.getUseTree().getPath().getSegment().getIdentifier().getText() = "__EcdsaPkHash" or // Workaround for the orphan rule, not part of public API usePrefix(u) = "dash_types" and diff --git a/pkgs/pkc/src/ecdsa/mod.rs b/pkgs/pkc/src/ecdsa/mod.rs index 83ebd70d..fcdc1f50 100644 --- a/pkgs/pkc/src/ecdsa/mod.rs +++ b/pkgs/pkc/src/ecdsa/mod.rs @@ -18,7 +18,7 @@ use dash_types::type_id::Unencodable; pub use error::EcdsaError; pub use public_bytes::{EcdsaPkBytes, ECDSA_PK_LEN}; -pub use public_hash::PubKeyHash; +pub use public_hash::EcdsaPkHash; pub use secret_bytes::{EcdsaSkBytes, ECDSA_SK_LEN}; pub use sig_bytes::{EcdsaSigBytes, ECDSA_SIG_LEN}; pub use sig_rec_bytes::EcdsaRecSigBytes; diff --git a/pkgs/pkc/src/ecdsa/public_bytes.rs b/pkgs/pkc/src/ecdsa/public_bytes.rs index bbd3f8a6..9a4e782f 100644 --- a/pkgs/pkc/src/ecdsa/public_bytes.rs +++ b/pkgs/pkc/src/ecdsa/public_bytes.rs @@ -6,7 +6,7 @@ //! secp256k1 public key byte bag. -use super::PubKeyHash; +use super::EcdsaPkHash; use crate::prelude::*; use bitcoin_hashes::{ripemd160, sha256}; @@ -102,7 +102,7 @@ impl BaseCodec for EcdsaPkBytes { impl_type!(EcdsaPkBytes); impl Hashable for EcdsaPkBytes { - type Hash = PubKeyHash; + type Hash = EcdsaPkHash; fn hash(&self) -> Self::Hash { Self::Hash::from(*ripemd160::Hash::hash(sha256::Hash::hash(self.as_bytes()).as_ref()).as_byte_array()) diff --git a/pkgs/pkc/src/ecdsa/public_hash.rs b/pkgs/pkc/src/ecdsa/public_hash.rs index 1d479fef..4e33961e 100644 --- a/pkgs/pkc/src/ecdsa/public_hash.rs +++ b/pkgs/pkc/src/ecdsa/public_hash.rs @@ -19,11 +19,11 @@ use dash_types::ArrayBuf; make_hash! { /// 20-byte public key hash. - PubKeyHash, 20 + EcdsaPkHash, 20 } #[cfg(feature = "codec")] -impl PubKeyHash { +impl EcdsaPkHash { /// Encode as a Base58Check address with the given version prefix. pub fn to_base58c(&self, prefix: u8) -> String { let mut buf = ArrayBuf::<21>::new(); diff --git a/pkgs/pkc/src/ecdsa/public_ops.rs b/pkgs/pkc/src/ecdsa/public_ops.rs index dce5a049..a1cf95c6 100644 --- a/pkgs/pkc/src/ecdsa/public_ops.rs +++ b/pkgs/pkc/src/ecdsa/public_ops.rs @@ -12,7 +12,7 @@ use super::sig_ops::EcdsaSignature; use super::sig_rec_ops::EcdsaRecSignature; use super::Compression; #[cfg(feature = "codec")] -use super::PubKeyHash; +use super::EcdsaPkHash; #[cfg(feature = "codec")] use dash_types::dlgt_codec; @@ -50,7 +50,7 @@ pub struct EcdsaPublicKey { } #[cfg(feature = "codec")] -dlgt_codec!(EcdsaPublicKey => EcdsaPkBytes, PubKeyHash, EcdsaError, ECDSA_PK_LEN + 2); +dlgt_codec!(EcdsaPublicKey => EcdsaPkBytes, EcdsaPkHash, EcdsaError, ECDSA_PK_LEN + 2); impl EcdsaPublicKey { pub(super) fn from_inner(inner: VerifyingKey, compressed: Compression) -> Self { diff --git a/pkgs/pkc/src/lib.rs b/pkgs/pkc/src/lib.rs index dcc439ae..26938faf 100644 --- a/pkgs/pkc/src/lib.rs +++ b/pkgs/pkc/src/lib.rs @@ -23,5 +23,5 @@ pub mod ecdsa; #[cfg(feature = "codec")] #[doc(hidden)] pub mod __private { - pub use crate::ecdsa::PubKeyHash as __PubKeyHash; + pub use crate::ecdsa::EcdsaPkHash as __EcdsaPkHash; } diff --git a/pkgs/script/src/lib.rs b/pkgs/script/src/lib.rs index 9ba867b5..5f5599f7 100644 --- a/pkgs/script/src/lib.rs +++ b/pkgs/script/src/lib.rs @@ -19,7 +19,7 @@ mod prelude; mod sigops; pub use addrs::{AddrParams, Recipient}; -pub use dash_pkc::__private::__PubKeyHash as PubKeyHash; +pub use dash_pkc::__private::__EcdsaPkHash as PubKeyHash; pub use dash_types::__private::__ScriptHash as ScriptHash; pub use opcode::Opcode; pub use sigops::legacy_sigop_count; From dc0ef699abe231d6ff143529745d5b4183bb8b0b Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 12 Sep 2026 18:34:53 +0530 Subject: [PATCH 07/11] pkc%refac(ecdsa): bridge gap against BLS API, add `{from,to}_bytes()` --- maint/codeql/rust/pkc.model.yml | 6 ------ pkgs/pkc/src/ecdsa/public_bytes.rs | 8 ++++++++ pkgs/pkc/src/ecdsa/public_ops.rs | 14 ++++++++++++++ pkgs/pkc/src/ecdsa/sig_bytes.rs | 5 +++++ pkgs/pkc/src/ecdsa/sig_ops.rs | 25 +++++++++++++------------ pkgs/pkc/src/ecdsa/sig_rec_ops.rs | 4 ++-- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/maint/codeql/rust/pkc.model.yml b/maint/codeql/rust/pkc.model.yml index bf9f7e55..a4e00adb 100644 --- a/maint/codeql/rust/pkc.model.yml +++ b/maint/codeql/rust/pkc.model.yml @@ -23,10 +23,6 @@ extensions: - ["Bls", "PublicKey", "ies_encrypt_multi"] # Encrypt multi # Key derivation - ["Bls", "SecretKey", "from_ikm"] # Run key material through a KDF - # Misc - - ["Bls", "PublicKey", "to_bytes"] - - ["Bls", "Signature", "from_bytes"] - - ["Bls", "Signature", "to_bytes"] # Scheme transform - ["Bls", "SecretKey", "to_scheme"] - ["Bls", "PublicKey", "to_scheme"] @@ -64,8 +60,6 @@ extensions: - ["Ecdsa", "PkHash", "to_base58c"] # Emit Base58c address - ["Ecdsa", "SkBytes", "from_wif"] # Read Base58c wallet import format - ["Ecdsa", "SkBytes", "to_wif"] # Emit Base58c wallet import format - - ["Ecdsa", "Signature", "from_compact"] # Read 64-byte r||s - - ["Ecdsa", "Signature", "to_compact"] # Emit 64-byte r||s # Malleability - ["Ecdsa", "Signature", "is_low_s"] # Query whether S is in the lower half - ["Ecdsa", "Signature", "normalize_s"] # Move S into the lower half diff --git a/pkgs/pkc/src/ecdsa/public_bytes.rs b/pkgs/pkc/src/ecdsa/public_bytes.rs index 9a4e782f..67a57729 100644 --- a/pkgs/pkc/src/ecdsa/public_bytes.rs +++ b/pkgs/pkc/src/ecdsa/public_bytes.rs @@ -138,6 +138,14 @@ impl EcdsaPkBytes { Some(Self::from_raw(prefix, bytes)) } + /// Copies out the raw SEC1 bytes. + /// + /// Allocates, unlike the fixed-width bags; how many bytes a key occupies + /// depends on the form it was parsed in. + pub fn to_bytes(&self) -> Vec { + self.as_bytes().to_vec() + } + /// Active byte length. pub fn size(&self) -> usize { self.prefix.size() diff --git a/pkgs/pkc/src/ecdsa/public_ops.rs b/pkgs/pkc/src/ecdsa/public_ops.rs index a1cf95c6..3e2f65d0 100644 --- a/pkgs/pkc/src/ecdsa/public_ops.rs +++ b/pkgs/pkc/src/ecdsa/public_ops.rs @@ -13,6 +13,7 @@ use super::sig_rec_ops::EcdsaRecSignature; use super::Compression; #[cfg(feature = "codec")] use super::EcdsaPkHash; +use crate::prelude::*; #[cfg(feature = "codec")] use dash_types::dlgt_codec; @@ -134,6 +135,19 @@ impl EcdsaPublicKey { self.form == PkForm::Hybrid } + /// Emit the key's own SEC1 layout. + /// + /// The form is whichever the key was parsed in; to name a form outright, use + /// [`to_compressed`](Self::to_compressed) or a sibling of it. The wire + /// image goes through the codec. + pub fn to_bytes(&self) -> Vec { + match self.form { + PkForm::Compressed => self.to_compressed().to_vec(), + PkForm::Uncompressed => self.to_uncompressed().to_vec(), + PkForm::Hybrid => self.to_hybrid().to_vec(), + } + } + /// Serialize as 33-byte compressed SEC1. pub fn to_compressed(&self) -> [u8; 33] { let pt = self.inner.to_sec1_point(true); diff --git a/pkgs/pkc/src/ecdsa/sig_bytes.rs b/pkgs/pkc/src/ecdsa/sig_bytes.rs index d679bf64..f396007c 100644 --- a/pkgs/pkc/src/ecdsa/sig_bytes.rs +++ b/pkgs/pkc/src/ecdsa/sig_bytes.rs @@ -62,6 +62,11 @@ impl Hashable for EcdsaSigBytes { } impl EcdsaSigBytes { + /// Wraps raw bytes without validation. + pub const fn from_bytes(bytes: [u8; ECDSA_SIG_LEN]) -> Self { + Self(bytes) + } + /// Borrow the raw inner bytes. pub const fn as_bytes(&self) -> &[u8; ECDSA_SIG_LEN] { &self.0 diff --git a/pkgs/pkc/src/ecdsa/sig_ops.rs b/pkgs/pkc/src/ecdsa/sig_ops.rs index f7de5fc6..54257ca4 100644 --- a/pkgs/pkc/src/ecdsa/sig_ops.rs +++ b/pkgs/pkc/src/ecdsa/sig_ops.rs @@ -44,16 +44,17 @@ impl EcdsaSignature { &self.0 } - /// Parse from 64-byte compact format (r || s). + /// Parse from the 64-byte layout (r || s). /// /// Accepts high-S signatures; see [`is_low_s`](Self::is_low_s) to reject - /// otherwise. + /// otherwise. For the DER encoding use [`from_der`](Self::from_der); for + /// the wire image use the codec. /// /// # Errors /// /// Returns [`EcdsaError::InvalidSignature`] when `r` or `s` is zero or not /// a scalar below the curve order. - pub fn from_compact(bytes: &[u8; ECDSA_SIG_LEN]) -> Result { + pub fn from_bytes(bytes: &[u8; ECDSA_SIG_LEN]) -> Result { Signature::from_slice(bytes) .map(Self) .map_err(|_| EcdsaError::InvalidSignature) @@ -83,8 +84,8 @@ impl EcdsaSignature { (normalized != self.0).then_some(Self(normalized)) } - /// Serialize as 64-byte compact format (r || s). - pub fn to_compact(&self) -> [u8; ECDSA_SIG_LEN] { + /// Emit the 64-byte layout (r || s). + pub fn to_bytes(&self) -> [u8; ECDSA_SIG_LEN] { self.0.to_bytes().into() } @@ -96,7 +97,7 @@ impl EcdsaSignature { impl Hash for EcdsaSignature { fn hash(&self, state: &mut H) { - self.to_compact().hash(state); + self.to_bytes().hash(state); } } @@ -143,11 +144,11 @@ impl PartialEq for EcdsaDerSig { } type_cvrt!(From for EcdsaSigBytes, |sig| { - Self::from(sig.to_compact()) + Self::from(sig.to_bytes()) }); type_cvrt!(TryFrom for EcdsaSignature, EcdsaError, |bytes| { - Self::from_compact(bytes.as_bytes()) + Self::from_bytes(bytes.as_bytes()) }); #[cfg(test)] @@ -162,8 +163,8 @@ mod tests { #[rstest] fn compact_roundtrip(alice_sig: EcdsaSignature) { - let bytes = alice_sig.to_compact(); - let restored = EcdsaSignature::from_compact(&bytes).unwrap(); + let bytes = alice_sig.to_bytes(); + let restored = EcdsaSignature::from_bytes(&bytes).unwrap(); assert_eq!(restored, alice_sig); } @@ -201,11 +202,11 @@ mod tests { #[rstest] fn normalize_s_flips_high_s_signature(alice_pk: EcdsaPublicKey, alice_sig: EcdsaSignature) { - let compact = alice_sig.to_compact(); + let compact = alice_sig.to_bytes(); let mut high_bytes = [0u8; 64]; high_bytes[..32].copy_from_slice(&compact[..32]); high_bytes[32..].copy_from_slice(&negate_scalar(&compact[32..])); - let high_sig = EcdsaSignature::from_compact(&high_bytes).unwrap(); + let high_sig = EcdsaSignature::from_bytes(&high_bytes).unwrap(); assert!(!high_sig.is_low_s()); let normalized = high_sig.normalize_s().unwrap(); diff --git a/pkgs/pkc/src/ecdsa/sig_rec_ops.rs b/pkgs/pkc/src/ecdsa/sig_rec_ops.rs index 5f588879..00eb7e46 100644 --- a/pkgs/pkc/src/ecdsa/sig_rec_ops.rs +++ b/pkgs/pkc/src/ecdsa/sig_rec_ops.rs @@ -89,7 +89,7 @@ impl EcdsaRecSignature { /// Serialize as 64-byte compact format (r || s). pub fn to_compact(&self) -> [u8; ECDSA_SIG_LEN] { - self.sig.to_compact() + self.sig.to_bytes() } } @@ -185,7 +185,7 @@ mod tests { let mut high_bytes = [0u8; 64]; high_bytes[..32].copy_from_slice(&compact[..32]); high_bytes[32..].copy_from_slice(&negate_scalar(&compact[32..])); - let high_sig = EcdsaSignature::from_compact(&high_bytes).unwrap(); + let high_sig = EcdsaSignature::from_bytes(&high_bytes).unwrap(); // The curve primitive rejects high-S signatures at recovery time (see // `EcdsaSignature::verify`), so only the invariant that normalizing From a101a6975d75918e6aa6bf18895c27736525f31e Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 15 Sep 2026 07:49:55 +0530 Subject: [PATCH 08/11] pkc%feat(ecdsa): add scalar and point tweaks to bridge gap with BLS API --- maint/codeql/rust/pkc.model.yml | 4 -- pkgs/pkc/src/ecdsa/error.rs | 3 ++ pkgs/pkc/src/ecdsa/public_ops.rs | 41 +++++++++++++++ pkgs/pkc/src/ecdsa/secret_ops.rs | 87 +++++++++++++++++++++++++++++++- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/maint/codeql/rust/pkc.model.yml b/maint/codeql/rust/pkc.model.yml index a4e00adb..38809e91 100644 --- a/maint/codeql/rust/pkc.model.yml +++ b/maint/codeql/rust/pkc.model.yml @@ -38,10 +38,6 @@ extensions: - ["Bls", "PublicKey", "derive_share"] # Get share from master keys - ["Bls", "Signature", "recover_shares"] # Get signature - ["Bls", "PublicKey", "recover_shares"] # Get master public key - # Tweaking - - ["Bls", "SecretKey", "add_tweak"] - - ["Bls", "PublicKey", "add_tweak"] - - ["Bls", "PublicKey", "mul_tweak"] # ECDSA # Compression - ["Ecdsa", "SecretKey", "is_compressed"] # Form the derived key will take diff --git a/pkgs/pkc/src/ecdsa/error.rs b/pkgs/pkc/src/ecdsa/error.rs index 6851e5be..acb229e9 100644 --- a/pkgs/pkc/src/ecdsa/error.rs +++ b/pkgs/pkc/src/ecdsa/error.rs @@ -19,6 +19,8 @@ pub enum EcdsaError { InvalidSecretKey, /// signature bytes are malformed InvalidSignature, + /// tweak is not below the order, or the result is zero or infinity + InvalidTweak, /// DER-encoded private key has invalid structure MalformedDer, /// recovery failed; no valid public key for this signature and message @@ -36,6 +38,7 @@ impl fmt::Display for EcdsaError { Self::InvalidRecoveryId => write!(f, "recovery id out of range (must be 0..4)"), Self::InvalidSecretKey => write!(f, "secret key bytes are not a valid scalar"), Self::InvalidSignature => write!(f, "signature bytes are malformed"), + Self::InvalidTweak => write!(f, "tweak is not below the order, or the result is zero or infinity"), Self::MalformedDer => write!(f, "DER-encoded private key has invalid structure"), Self::RecoveryFailed => write!(f, "recovery failed; no valid public key"), Self::SigningFailed => write!(f, "signing failed"), diff --git a/pkgs/pkc/src/ecdsa/public_ops.rs b/pkgs/pkc/src/ecdsa/public_ops.rs index 3e2f65d0..071d05ac 100644 --- a/pkgs/pkc/src/ecdsa/public_ops.rs +++ b/pkgs/pkc/src/ecdsa/public_ops.rs @@ -8,6 +8,8 @@ use super::error::EcdsaError; use super::public_bytes::{EcdsaPkBytes, Sec1Byte, ECDSA_PK_LEN}; +use super::secret_bytes::ECDSA_SK_LEN; +use super::secret_ops::tweak_scalar; use super::sig_ops::EcdsaSignature; use super::sig_rec_ops::EcdsaRecSignature; use super::Compression; @@ -21,6 +23,7 @@ use dash_types::type_cvrt; #[cfg(feature = "codec")] use dash_types::type_id::{TypeId, Unencodable}; use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey}; +use k256::ProjectivePoint; use core::hash::{Hash, Hasher}; @@ -125,6 +128,44 @@ impl EcdsaPublicKey { } } + /// Add `tweak * G` to the point. + /// + /// The serialization form carries over from `self`. + /// + /// # Errors + /// + /// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the curve + /// order, or when the sum is the point at infinity. + pub fn add_tweak(&self, tweak: &[u8; ECDSA_SK_LEN]) -> Result { + let scalar = tweak_scalar(tweak)?; + self.tweaked(ProjectivePoint::from(self.inner.as_affine()) + ProjectivePoint::GENERATOR * scalar) + } + + /// Multiply the point by `tweak`. + /// + /// The serialization form carries over from `self`. + /// + /// # Errors + /// + /// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the curve + /// order, or when the product is the point at infinity. + pub fn mul_tweak(&self, tweak: &[u8; ECDSA_SK_LEN]) -> Result { + let scalar = tweak_scalar(tweak)?; + self.tweaked(ProjectivePoint::from(self.inner.as_affine()) * scalar) + } + + /// Rewrap a tweaked point, keeping the serialization form. + /// + /// # Errors + /// + /// Returns [`EcdsaError::InvalidTweak`] when the point is at infinity, which + /// is no key: the tweak cancelled the one it was applied to. + fn tweaked(&self, point: ProjectivePoint) -> Result { + let inner = VerifyingKey::from_affine(point.to_affine()).map_err(|_| EcdsaError::InvalidTweak)?; + + Ok(Self { inner, form: self.form }) + } + /// Whether this key serializes as compressed. pub fn is_compressed(&self) -> bool { self.form == PkForm::Compressed diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index c6336633..a6f86831 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -27,10 +27,12 @@ use dash_types::{impl_stype, type_id::TypeId, ArrayBuf}; #[cfg(feature = "codec")] use dash_types::{Hashable, Numeric}; use k256::ecdsa::{signature::hazmat::PrehashSigner, SigningKey}; +use k256::elliptic_curve::ff::PrimeField; use k256::elliptic_curve::ops::Neg; use k256::elliptic_curve::Generate; #[cfg(feature = "codec")] use k256::{elliptic_curve::sec1::ToSec1Point, AffinePoint}; +use k256::{NonZeroScalar, Scalar}; use rand_core::CryptoRng; use zeroize::{Zeroize, Zeroizing}; @@ -171,6 +173,21 @@ impl Hashable for EcdsaSecretKey { } } +/// Parse a tweak as a scalar below the curve order. +/// +/// Shared with the point tweaks, which bound a tweak the same way: `from_repr` +/// is the canonical parse, so a value at or above the order is refused rather +/// than reduced into range behind the caller's back. +/// +/// # Errors +/// +/// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the order. +pub(super) fn tweak_scalar(tweak: &[u8; ECDSA_SK_LEN]) -> Result { + Scalar::from_repr((*tweak).into()) + .into_option() + .ok_or(EcdsaError::InvalidTweak) +} + impl EcdsaSecretKey { /// Parse a secret key from a 32-byte big-endian scalar. /// @@ -200,6 +217,24 @@ impl EcdsaSecretKey { self.compressed } + /// Add `tweak` to the scalar, modulo the curve order. + /// + /// # Errors + /// + /// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the curve + /// order, or when the sum is zero. Zero is not a valid secret key, so the + /// sum is refused rather than returned as one. + pub fn add_tweak(&self, tweak: &[u8; ECDSA_SK_LEN]) -> Result { + let scalar = tweak_scalar(tweak)?; + let sum = *self.inner.as_nonzero_scalar().as_ref() + scalar; + let sum = NonZeroScalar::new(sum).into_option().ok_or(EcdsaError::InvalidTweak)?; + + Ok(Self { + inner: SigningKey::from(sum), + compressed: self.compressed, + }) + } + /// Negate the secret scalar in place. pub fn negate(&mut self) { let neg = self.inner.as_nonzero_scalar().neg(); @@ -286,8 +321,9 @@ type_cvrt!(TryFrom for EcdsaSecretKey, EcdsaError, |bytes| { #[expect(clippy::ptr_arg, clippy::unwrap_used, reason = "test code")] mod tests { use super::OID_PRIME_FIELD; + use crate::ecdsa::curve_consts::ORDER; use crate::ecdsa::tests::*; - use crate::ecdsa::{Compression, EcdsaPublicKey, EcdsaSecretKey}; + use crate::ecdsa::{Compression, EcdsaError, EcdsaPublicKey, EcdsaSecretKey, ECDSA_SK_LEN}; use crate::prelude::*; use dash_dev::{arr_from_hex, Corpus}; @@ -415,6 +451,55 @@ mod tests { ); } + #[rstest] + fn tweaking_agrees_on_both_sides(alice_sk: EcdsaSecretKey, bob_sk: EcdsaSecretKey) { + // (a + t)G has to equal aG + tG, or the same tweak applied to the two + // halves of a key pair would part them. + let tweak = *bob_sk.to_bytes(); + let tweaked_sk = alice_sk.add_tweak(&tweak).unwrap(); + let tweaked_pk = alice_sk.public_key().add_tweak(&tweak).unwrap(); + + assert_eq!(tweaked_sk.public_key(), tweaked_pk); + assert!(tweaked_sk.verify_pubkey(&tweaked_pk)); + } + + #[rstest] + fn a_tweak_at_or_above_the_order_is_refused(alice_sk: EcdsaSecretKey) { + assert_eq!(alice_sk.add_tweak(ORDER), Err(EcdsaError::InvalidTweak)); + assert_eq!(alice_sk.add_tweak(&[0xff; ECDSA_SK_LEN]), Err(EcdsaError::InvalidTweak)); + } + + #[rstest] + fn a_tweak_summing_to_zero_is_refused(alice_sk: EcdsaSecretKey) { + // order - a, so a + t == 0, which is no scalar a key can hold. + let mut tweak = *ORDER; + let mut borrow = 0i16; + let scalar = *alice_sk.to_bytes(); + + for i in (0..ECDSA_SK_LEN).rev() { + let diff = i16::from(tweak[i]) - i16::from(scalar[i]) - borrow; + borrow = i16::from(diff < 0); + tweak[i] = diff.rem_euclid(256) as u8; + } + + assert_eq!(alice_sk.add_tweak(&tweak), Err(EcdsaError::InvalidTweak)); + } + + #[rstest] + fn multiplying_a_point_matches_multiplying_the_scalar(alice_sk: EcdsaSecretKey, bob_sk: EcdsaSecretKey) { + // t(aG) == a(tG). Multiplying a point commutes with multiplying the + // scalar that made it. + let factor = *bob_sk.to_bytes(); + let product = alice_sk.public_key().mul_tweak(&factor).unwrap(); + let expected = EcdsaSecretKey::from_bytes(&factor, Compression::Compressed) + .unwrap() + .public_key() + .mul_tweak(&alice_sk.to_bytes()) + .unwrap(); + + assert_eq!(product, expected); + } + #[rstest] fn negate_changes_key(alice_sk: EcdsaSecretKey) { let original_bytes = alice_sk.to_bytes(); From 5e2016a0973b7af83ad79f6a5c6933e6b5e37444 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 15 Sep 2026 17:20:32 +0530 Subject: [PATCH 09/11] pkc%feat(ecdsa): hardcode generator and verify against `k256` --- pkgs/pkc/src/ecdsa/curve_consts.rs | 29 +++++++++++++++++++++++++++++ pkgs/pkc/src/ecdsa/secret_ops.rs | 11 ++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/pkgs/pkc/src/ecdsa/curve_consts.rs b/pkgs/pkc/src/ecdsa/curve_consts.rs index e23c0730..3b211891 100644 --- a/pkgs/pkc/src/ecdsa/curve_consts.rs +++ b/pkgs/pkc/src/ecdsa/curve_consts.rs @@ -22,3 +22,32 @@ pub(super) const PRIME: &[u8; ECDSA_SK_LEN] = &hex!("fffffffffffffffffffffffffff /// The group order. pub(super) const ORDER: &[u8; ECDSA_SK_LEN] = &hex!("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); + +/// The generator point in SEC1 uncompressed form. +pub(super) const GENERATOR: &[u8; 65] = &hex!( + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\ + 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" +); + +/// The generator point in SEC1 compressed form. +pub(super) const GENERATOR_COMPRESSED: [u8; 33] = + hex!("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); + +#[cfg(test)] +mod tests { + use super::{GENERATOR, GENERATOR_COMPRESSED, ORDER}; + + use hex_conservative::DisplayHex; + use k256::elliptic_curve::sec1::ToSec1Point; + use k256::elliptic_curve::PrimeField; + use rstest::rstest; + + #[rstest] + fn constants_match_k256() { + let generator = k256::AffinePoint::GENERATOR; + + assert_eq!(generator.to_sec1_point(false).as_bytes(), &GENERATOR[..]); + assert_eq!(generator.to_sec1_point(true).as_bytes(), &GENERATOR_COMPRESSED[..]); + assert_eq!(ORDER.to_upper_hex_string(), ::MODULUS); + } +} diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index a6f86831..efbad474 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -7,7 +7,7 @@ //! secp256k1 secret key. #[cfg(feature = "codec")] -use super::curve_consts::{DER_SIZES, OID_PRIME_FIELD, ORDER, PRIME}; +use super::curve_consts::{DER_SIZES, GENERATOR, GENERATOR_COMPRESSED, OID_PRIME_FIELD, ORDER, PRIME}; use super::error::EcdsaError; use super::public_ops::EcdsaPublicKey; use super::secret_bytes::{EcdsaSkBytes, ECDSA_SK_LEN}; @@ -30,8 +30,6 @@ use k256::ecdsa::{signature::hazmat::PrehashSigner, SigningKey}; use k256::elliptic_curve::ff::PrimeField; use k256::elliptic_curve::ops::Neg; use k256::elliptic_curve::Generate; -#[cfg(feature = "codec")] -use k256::{elliptic_curve::sec1::ToSec1Point, AffinePoint}; use k256::{NonZeroScalar, Scalar}; use rand_core::CryptoRng; use zeroize::{Zeroize, Zeroizing}; @@ -132,8 +130,11 @@ impl BaseCodec for EcdsaSecretKey { let scalar = self.to_bytes(); let public = self.inner.verifying_key().to_sec1_point(self.compressed); let public = public.as_bytes(); - let generator = AffinePoint::GENERATOR.to_sec1_point(self.compressed); - let generator = generator.as_bytes(); + let generator: &[u8] = if self.compressed { + &GENERATOR_COMPRESSED + } else { + GENERATOR + }; let point_len = public.len(); let params_len = point_len + 97; From c257972767d12e770ea5012984255ee4601e96b1 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 12 Sep 2026 22:17:33 +0530 Subject: [PATCH 10/11] pkc%perf(ecdsa): swap `k256` for `libsecp256k1` for perf, low-R grinding --- Cargo.lock | 570 ++++++++++------------------- maint/codeql/rust/zeroize.ql | 4 - pkgs/pkc/Cargo.toml | 9 +- pkgs/pkc/bench/ecdsa.rs | 8 +- pkgs/pkc/src/ecdsa/curve_consts.rs | 19 - pkgs/pkc/src/ecdsa/error.rs | 3 - pkgs/pkc/src/ecdsa/public_ops.rs | 62 ++-- pkgs/pkc/src/ecdsa/secret_ops.rs | 184 ++++++---- pkgs/pkc/src/ecdsa/sig_ops.rs | 32 +- pkgs/pkc/src/ecdsa/sig_rec_ops.rs | 13 +- pkgs/pkc/src/ecdsa/tests.rs | 4 +- 11 files changed, 373 insertions(+), 535 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db771ae4..9b39077e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures 0.2.17", + "cpufeatures", "zeroize", ] @@ -50,12 +50,6 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" -[[package]] -name = "base16ct" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" - [[package]] name = "base58ck" version = "0.4.0" @@ -154,9 +148,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.2" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block-buffer" @@ -167,15 +161,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - [[package]] name = "blst" version = "0.3.17" @@ -219,9 +204,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.6" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -279,15 +264,15 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.7", + "crypto-common", "inout", ] [[package]] name = "clap" -version = "4.6.7" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" dependencies = [ "clap_builder", "clap_derive", @@ -295,9 +280,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.7" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" dependencies = [ "anstyle", "clap_lex", @@ -306,27 +291,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.7" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] name = "clap_lex" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" - -[[package]] -name = "cmov" -version = "0.5.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "condtype" @@ -345,24 +324,12 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpubits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -372,20 +339,11 @@ dependencies = [ "libc", ] -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - [[package]] name = "crossbeam-deque" -version = "0.8.8" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -393,18 +351,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.21" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.23" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -412,51 +370,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" -dependencies = [ - "cpubits", - "ctutils", - "hybrid-array", - "num-traits", - "rand_core", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", - "rand_core", -] - -[[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", - "subtle", -] - [[package]] name = "dash-dev" version = "0.0.0" @@ -550,11 +473,11 @@ dependencies = [ "getrandom", "group", "hex-conservative 1.3.0", - "k256", "rand_core", "rstest", + "secp256k1", "serde", - "sha2 0.10.9", + "sha2", "subtle", "zeroize", ] @@ -632,36 +555,14 @@ dependencies = [ "xxhash-rust", ] -[[package]] -name = "der" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.7", -] - -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "const-oid", - "crypto-common 0.2.2", - "ctutils", + "block-buffer", + "crypto-common", ] [[package]] @@ -672,7 +573,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -700,44 +601,11 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "ecdsa" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" -dependencies = [ - "der", - "digest 0.11.3", - "elliptic-curve", - "rfc6979", - "signature", - "zeroize", -] - [[package]] name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" - -[[package]] -name = "elliptic-curve" -version = "0.14.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" -dependencies = [ - "base16ct", - "crypto-bigint", - "crypto-common 0.2.2", - "digest 0.11.3", - "ff", - "group", - "hybrid-array", - "rand_core", - "sec1", - "subtle", - "zeroize", -] +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "encode_unicode" @@ -773,9 +641,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.12" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -794,26 +662,26 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-macro" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] name = "futures-task" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-timer" @@ -823,9 +691,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-macro", @@ -836,9 +704,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -911,9 +779,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex-conservative" @@ -939,26 +807,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.3", -] - -[[package]] -name = "hybrid-array" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" -dependencies = [ - "subtle", - "typenum", - "zeroize", -] - [[package]] name = "iana-time-zone" version = "0.1.65" @@ -985,22 +833,21 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", - "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locale_core" -version = "2.1.1" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", @@ -1009,61 +856,99 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ + "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "utf8_iter", + "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" -version = "2.1.2" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ + "displaydoc", "icu_collections", - "icu_locale_core", + "icu_locid_transform", "icu_properties_data", "icu_provider", - "zerotrie", + "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" -version = "2.1.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", - "icu_locale_core", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", "writeable", "yoke", "zerofrom", - "zerotrie", "zerovec", ] +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "idna" version = "1.1.0" @@ -1077,9 +962,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", @@ -1087,9 +972,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.2" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -1133,9 +1018,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.105" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -1153,20 +1038,6 @@ dependencies = [ "serde", ] -[[package]] -name = "k256" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f50113171a713f4a4231ef82eb26703607139b35dcb56241f0ceab2ae1f7d8" -dependencies = [ - "cpubits", - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.11.0", - "wnaf", -] - [[package]] name = "libc" version = "0.2.189" @@ -1175,9 +1046,9 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libgit2-sys" -version = "0.18.8+1.9.7" +version = "0.18.7+1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7c568b25d7489bc3fb2988ed69ab111d2944d2f5fec3d5c987fe545ea97b50" +checksum = "23c7391e4b9f4ffab1a624223cc1d7385ff9a678f490768add717de7ea2f4d89" dependencies = [ "cc", "libc", @@ -1211,15 +1082,15 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "log" -version = "0.4.34" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" @@ -1269,9 +1140,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -1279,9 +1150,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -1289,9 +1160,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", @@ -1302,9 +1173,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.9.1" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", ] @@ -1327,49 +1198,15 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.34" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "zerovec", -] - -[[package]] -name = "primefield" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" -dependencies = [ - "crypto-bigint", - "crypto-common 0.2.2", - "ff", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "primeorder" -version = "0.14.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" -dependencies = [ - "elliptic-curve", - "primefield", - "wnaf", -] +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "proc-macro-crate" @@ -1377,7 +1214,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.15+spec-1.1.0", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] @@ -1471,16 +1308,6 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" -[[package]] -name = "rfc6979" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" -dependencies = [ - "crypto-bigint", - "hmac", -] - [[package]] name = "rstest" version = "0.25.0" @@ -1540,17 +1367,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] -name = "sec1" -version = "0.8.1" +name = "secp256k1" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +checksum = "d7f404a8dab7a7a5a631e741d8699aa9c8e1d689fc26ccf897c7187565490b69" dependencies = [ - "base16ct", - "ctutils", - "der", - "hybrid-array", - "subtle", - "zeroize", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b2992d4a3cd244539a7d5d0966aadbe5ca7fb868a5d7e38c29499b9e709bbd" +dependencies = [ + "cc", ] [[package]] @@ -1590,7 +1421,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.3", ] [[package]] @@ -1622,19 +1453,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "digest 0.11.3", + "cpufeatures", + "digest", ] [[package]] @@ -1643,16 +1463,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" -[[package]] -name = "signature" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" -dependencies = [ - "digest 0.11.3", - "rand_core", -] - [[package]] name = "slab" version = "0.4.12" @@ -1661,9 +1471,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "stable_deref_trait" @@ -1690,9 +1500,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.5" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1744,9 +1554,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", @@ -1798,9 +1608,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.15+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", @@ -1859,6 +1669,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -1879,9 +1695,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasm-bindgen" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -1892,9 +1708,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1902,22 +1718,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.128" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] @@ -2148,22 +1964,16 @@ dependencies = [ ] [[package]] -name = "wnaf" -version = "0.14.1" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" -dependencies = [ - "ff", - "group", - "hybrid-array", - "primefield", -] +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" -version = "0.6.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xxhash-rust" @@ -2173,10 +1983,11 @@ checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" [[package]] name = "yoke" -version = "0.8.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -2184,9 +1995,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", @@ -2196,18 +2007,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.57" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.57" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", @@ -2255,22 +2066,11 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - [[package]] name = "zerovec" -version = "0.11.8" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", @@ -2279,13 +2079,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "3e3c6377872d72510393f688a555d7097b0f741995c7a00f0407f786dd486b2d" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 2.0.119", ] [[package]] diff --git a/maint/codeql/rust/zeroize.ql b/maint/codeql/rust/zeroize.ql index 0c9b8ac7..999e527c 100644 --- a/maint/codeql/rust/zeroize.ql +++ b/maint/codeql/rust/zeroize.ql @@ -71,10 +71,6 @@ predicate wipesSelf(TypeItem t) { predicate externalWiper(TypeItem t) { not isWorkspaceFile(fileOf(t)) and ( - // `k256::ecdsa::SigningKey` derives `ZeroizeOnDrop`. - t.getName().getText() = "SigningKey" and - fileOf(t).getAbsolutePath().matches("%/ecdsa-%/src/signing.rs") - or // `blst::{min_pk,min_sig}::SecretKey` are declared `#[zeroize(drop)]`. t.getName().getText() = "SecretKey" and fileOf(t).getAbsolutePath().matches("%/blst-%/src/lib.rs") diff --git a/pkgs/pkc/Cargo.toml b/pkgs/pkc/Cargo.toml index ca5c31e8..49d8096e 100644 --- a/pkgs/pkc/Cargo.toml +++ b/pkgs/pkc/Cargo.toml @@ -24,10 +24,9 @@ dash-types = { version = "0.1.0-beta", path = "../types", default-features = fal hex-conservative = { workspace = true, features = [ "alloc", ] } -k256 = { version = "0.14", default-features = false, features = [ - "arithmetic", - "ecdsa", - "sha256", +secp256k1 = { version = "0.33.1", default-features = false, features = [ + "alloc", + "recovery", ], optional = true } rand_core = { workspace = true, optional = true } rstest = { version = "0.25", optional = true } @@ -65,7 +64,7 @@ codec = [ "dash-num/codec", "dash-types/codec", ] -ecdsa = ["dep:k256", "dep:rand_core"] +ecdsa = ["dep:rand_core", "dep:secp256k1"] serde = ["codec", "dep:serde", "dash-num/serde", "dash-types/serde"] full = ["bls", "codec", "ecdsa", "serde", "std", "tests"] tests = ["std", "dep:rstest"] diff --git a/pkgs/pkc/bench/ecdsa.rs b/pkgs/pkc/bench/ecdsa.rs index 8e95d260..f9372699 100644 --- a/pkgs/pkc/bench/ecdsa.rs +++ b/pkgs/pkc/bench/ecdsa.rs @@ -18,7 +18,7 @@ fn sign(bencher: divan::Bencher) { let sk = test_key(); bencher.counter(divan::counter::ItemsCount::new(1u32)).bench(|| { let msg = message_hash(42); - sk.sign(&msg).unwrap() + sk.sign(&msg) }); } @@ -26,7 +26,7 @@ fn sign(bencher: divan::Bencher) { fn verify(bencher: divan::Bencher) { let sk = test_key(); let msg = message_hash(99); - let sig = sk.sign(&msg).unwrap(); + let sig = sk.sign(&msg); let pk = sk.public_key(); bencher .counter(divan::counter::ItemsCount::new(1u32)) @@ -38,14 +38,14 @@ fn sign_recoverable(bencher: divan::Bencher) { let sk = test_key(); bencher .counter(divan::counter::ItemsCount::new(1u32)) - .bench(|| sk.sign_recoverable(&message_hash(7)).unwrap()); + .bench(|| sk.sign_recoverable(&message_hash(7))); } #[divan::bench] fn recover(bencher: divan::Bencher) { let sk = test_key(); let msg = message_hash(55); - let sig = sk.sign_recoverable(&msg).unwrap(); + let sig = sk.sign_recoverable(&msg); bencher .counter(divan::counter::ItemsCount::new(1u32)) .bench(|| EcdsaPublicKey::recover(&msg, &sig).unwrap()); diff --git a/pkgs/pkc/src/ecdsa/curve_consts.rs b/pkgs/pkc/src/ecdsa/curve_consts.rs index 3b211891..ea462df3 100644 --- a/pkgs/pkc/src/ecdsa/curve_consts.rs +++ b/pkgs/pkc/src/ecdsa/curve_consts.rs @@ -32,22 +32,3 @@ pub(super) const GENERATOR: &[u8; 65] = &hex!( /// The generator point in SEC1 compressed form. pub(super) const GENERATOR_COMPRESSED: [u8; 33] = hex!("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); - -#[cfg(test)] -mod tests { - use super::{GENERATOR, GENERATOR_COMPRESSED, ORDER}; - - use hex_conservative::DisplayHex; - use k256::elliptic_curve::sec1::ToSec1Point; - use k256::elliptic_curve::PrimeField; - use rstest::rstest; - - #[rstest] - fn constants_match_k256() { - let generator = k256::AffinePoint::GENERATOR; - - assert_eq!(generator.to_sec1_point(false).as_bytes(), &GENERATOR[..]); - assert_eq!(generator.to_sec1_point(true).as_bytes(), &GENERATOR_COMPRESSED[..]); - assert_eq!(ORDER.to_upper_hex_string(), ::MODULUS); - } -} diff --git a/pkgs/pkc/src/ecdsa/error.rs b/pkgs/pkc/src/ecdsa/error.rs index acb229e9..de543166 100644 --- a/pkgs/pkc/src/ecdsa/error.rs +++ b/pkgs/pkc/src/ecdsa/error.rs @@ -25,8 +25,6 @@ pub enum EcdsaError { MalformedDer, /// recovery failed; no valid public key for this signature and message RecoveryFailed, - /// signing operation failed - SigningFailed, /// signature verification failed VerifyFailed, } @@ -41,7 +39,6 @@ impl fmt::Display for EcdsaError { Self::InvalidTweak => write!(f, "tweak is not below the order, or the result is zero or infinity"), Self::MalformedDer => write!(f, "DER-encoded private key has invalid structure"), Self::RecoveryFailed => write!(f, "recovery failed; no valid public key"), - Self::SigningFailed => write!(f, "signing failed"), Self::VerifyFailed => write!(f, "signature verification failed"), } } diff --git a/pkgs/pkc/src/ecdsa/public_ops.rs b/pkgs/pkc/src/ecdsa/public_ops.rs index 071d05ac..b56ff8da 100644 --- a/pkgs/pkc/src/ecdsa/public_ops.rs +++ b/pkgs/pkc/src/ecdsa/public_ops.rs @@ -22,8 +22,8 @@ use dash_types::dlgt_codec; use dash_types::type_cvrt; #[cfg(feature = "codec")] use dash_types::type_id::{TypeId, Unencodable}; -use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey}; -use k256::ProjectivePoint; +use secp256k1::ecdsa::RecoverableSignature; +use secp256k1::{Message, PublicKey, Scalar}; use core::hash::{Hash, Hasher}; @@ -49,7 +49,7 @@ pub(super) enum PkForm { #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr(feature = "serde", serde(into = "EcdsaPkBytes", try_from = "EcdsaPkBytes"))] pub struct EcdsaPublicKey { - inner: VerifyingKey, + inner: PublicKey, form: PkForm, } @@ -57,7 +57,7 @@ pub struct EcdsaPublicKey { dlgt_codec!(EcdsaPublicKey => EcdsaPkBytes, EcdsaPkHash, EcdsaError, ECDSA_PK_LEN + 2); impl EcdsaPublicKey { - pub(super) fn from_inner(inner: VerifyingKey, compressed: Compression) -> Self { + pub(super) fn from_inner(inner: PublicKey, compressed: Compression) -> Self { Self { inner, form: match compressed { @@ -67,8 +67,8 @@ impl EcdsaPublicKey { } } - /// Borrow the inner verifying key. - pub(super) fn as_inner(&self) -> &VerifyingKey { + /// Borrow the inner curve point. + pub(super) fn as_inner(&self) -> &PublicKey { &self.inner } @@ -112,7 +112,7 @@ impl EcdsaPublicKey { let mut buf = [0u8; ECDSA_PK_LEN + 1]; buf.copy_from_slice(bytes); buf[0] = Sec1Byte::Uncomp.to_base(); - VerifyingKey::from_sec1_bytes(&buf) + PublicKey::from_slice(&buf) .map(|key| Self { inner: key, form: PkForm::Hybrid, @@ -121,7 +121,7 @@ impl EcdsaPublicKey { } _ => { let compressed = Compression::from(prefix.is_some_and(|s| s.is_compressed())); - VerifyingKey::from_sec1_bytes(bytes) + PublicKey::from_slice(bytes) .map(|key| Self::from_inner(key, compressed)) .map_err(|_| EcdsaError::InvalidPublicKey) } @@ -137,8 +137,7 @@ impl EcdsaPublicKey { /// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the curve /// order, or when the sum is the point at infinity. pub fn add_tweak(&self, tweak: &[u8; ECDSA_SK_LEN]) -> Result { - let scalar = tweak_scalar(tweak)?; - self.tweaked(ProjectivePoint::from(self.inner.as_affine()) + ProjectivePoint::GENERATOR * scalar) + self.tweaked(tweak, PublicKey::add_exp_tweak) } /// Multiply the point by `tweak`. @@ -150,20 +149,28 @@ impl EcdsaPublicKey { /// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the curve /// order, or when the product is the point at infinity. pub fn mul_tweak(&self, tweak: &[u8; ECDSA_SK_LEN]) -> Result { - let scalar = tweak_scalar(tweak)?; - self.tweaked(ProjectivePoint::from(self.inner.as_affine()) * scalar) + self.tweaked(tweak, PublicKey::mul_tweak) } - /// Rewrap a tweaked point, keeping the serialization form. + /// Apply `op` to the point with `tweak`, keeping the serialization form. /// /// # Errors /// - /// Returns [`EcdsaError::InvalidTweak`] when the point is at infinity, which - /// is no key: the tweak cancelled the one it was applied to. - fn tweaked(&self, point: ProjectivePoint) -> Result { - let inner = VerifyingKey::from_affine(point.to_affine()).map_err(|_| EcdsaError::InvalidTweak)?; + /// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the curve + /// order, or when the result is the point at infinity, which is no key; the + /// tweak cancelled the key it was applied to. + fn tweaked( + &self, + tweak: &[u8; ECDSA_SK_LEN], + op: impl Fn(PublicKey, &Scalar) -> Result, + ) -> Result { + let scalar = tweak_scalar(tweak)?; + let point = op(self.inner, &scalar).map_err(|_| EcdsaError::InvalidTweak)?; - Ok(Self { inner, form: self.form }) + Ok(Self { + inner: point, + form: self.form, + }) } /// Whether this key serializes as compressed. @@ -191,10 +198,7 @@ impl EcdsaPublicKey { /// Serialize as 33-byte compressed SEC1. pub fn to_compressed(&self) -> [u8; 33] { - let pt = self.inner.to_sec1_point(true); - let mut out = [0u8; 33]; - out.copy_from_slice(pt.as_bytes()); - out + self.inner.serialize() } /// Serialize as 65-byte hybrid SEC1, restating the Y parity in the header. @@ -206,10 +210,7 @@ impl EcdsaPublicKey { /// Serialize as 65-byte uncompressed SEC1. pub fn to_uncompressed(&self) -> [u8; 65] { - let pt = self.inner.to_sec1_point(false); - let mut out = [0u8; 65]; - out.copy_from_slice(pt.as_bytes()); - out + self.inner.serialize_uncompressed() } /// Recover a public key from a signature and its embedded recovery metadata. @@ -220,7 +221,10 @@ impl EcdsaPublicKey { /// signature and message. The embedded recovery id needs no check: it is in /// `0..=3` by construction. pub fn recover(msg_hash: &[u8; 32], sig: &EcdsaRecSignature) -> Result { - VerifyingKey::recover_from_prehash(msg_hash, sig.signature().as_inner(), sig.backend_recovery_id()) + // The compact form is the only way in; the recoverable signature is held + // as scalars plus metadata, so the backend's own type is assembled here. + RecoverableSignature::from_compact(&sig.to_compact(), sig.backend_recovery_id()) + .and_then(|rec| rec.recover(Message::from_digest(*msg_hash))) .map(|key| Self::from_inner(key, Compression::from(sig.is_compressed()))) .map_err(|_| EcdsaError::RecoveryFailed) } @@ -239,7 +243,7 @@ impl EcdsaPublicKey { pub fn verify(&self, msg_hash: &[u8; 32], sig: impl AsRef) -> Result<(), EcdsaError> { self .inner - .verify_prehash(msg_hash, sig.as_ref().as_inner()) + .verify(Message::from_digest(*msg_hash), sig.as_ref().as_inner()) .map_err(|_| EcdsaError::VerifyFailed) } } @@ -383,7 +387,7 @@ mod tests { #[rstest] fn recover_roundtrip(alice_pk: EcdsaPublicKey, alice_sk: EcdsaSecretKey, alice_rec_sig: EcdsaRecSignature) { - let compact_sig = EcdsaRecSigBytes::from(alice_sk.sign_recoverable(&MSG).unwrap()); + let compact_sig = EcdsaRecSigBytes::from(alice_sk.sign_recoverable(&MSG)); let restored = EcdsaRecSignature::try_from(compact_sig).unwrap(); assert_eq!(EcdsaPublicKey::recover(&MSG, &restored).unwrap(), alice_pk); assert_eq!(EcdsaPublicKey::recover(&MSG, &alice_rec_sig).unwrap(), alice_pk); diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index efbad474..90535ffa 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -26,13 +26,10 @@ use dash_types::type_cvrt; use dash_types::{impl_stype, type_id::TypeId, ArrayBuf}; #[cfg(feature = "codec")] use dash_types::{Hashable, Numeric}; -use k256::ecdsa::{signature::hazmat::PrehashSigner, SigningKey}; -use k256::elliptic_curve::ff::PrimeField; -use k256::elliptic_curve::ops::Neg; -use k256::elliptic_curve::Generate; -use k256::{NonZeroScalar, Scalar}; use rand_core::CryptoRng; -use zeroize::{Zeroize, Zeroizing}; +use secp256k1::ecdsa::RecoverableSignature; +use secp256k1::{Message, PublicKey, Scalar, SecretKey}; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; use core::fmt; @@ -70,7 +67,8 @@ fn der_uint(buf: &mut impl EncodeBuf, bytes: &[u8]) { #[derive(Clone)] #[cfg_attr(feature = "codec", derive(TypeId))] pub struct EcdsaSecretKey { - inner: SigningKey, + inner: SecretKey, + public: PublicKey, compressed: bool, } @@ -128,8 +126,9 @@ impl BaseCodec for EcdsaSecretKey { /// and zeroize or drop it themselves once done. fn encode(&self, buf: &mut impl EncodeBuf) { let scalar = self.to_bytes(); - let public = self.inner.verifying_key().to_sec1_point(self.compressed); - let public = public.as_bytes(); + let compressed = self.public.serialize(); + let uncompressed = self.public.serialize_uncompressed(); + let public: &[u8] = if self.compressed { &compressed } else { &uncompressed }; let generator: &[u8] = if self.compressed { &GENERATOR_COMPRESSED } else { @@ -176,17 +175,15 @@ impl Hashable for EcdsaSecretKey { /// Parse a tweak as a scalar below the curve order. /// -/// Shared with the point tweaks, which bound a tweak the same way: `from_repr` -/// is the canonical parse, so a value at or above the order is refused rather -/// than reduced into range behind the caller's back. +/// Shared with the point tweaks, which bound a tweak the same way; the parse +/// is canonical, so a value at or above the order is refused rather than +/// reduced into range behind the caller's back. /// /// # Errors /// /// Returns [`EcdsaError::InvalidTweak`] when `tweak` is not below the order. pub(super) fn tweak_scalar(tweak: &[u8; ECDSA_SK_LEN]) -> Result { - Scalar::from_repr((*tweak).into()) - .into_option() - .ok_or(EcdsaError::InvalidTweak) + Scalar::from_be_bytes(*tweak).map_err(|_| EcdsaError::InvalidTweak) } impl EcdsaSecretKey { @@ -197,18 +194,32 @@ impl EcdsaSecretKey { /// Returns [`EcdsaError::InvalidSecretKey`] when the scalar is zero or not /// below the curve order. pub fn from_bytes(bytes: &[u8; 32], compressed: Compression) -> Result { - SigningKey::from_bytes(bytes.into()) - .map(|key| Self { - inner: key, - compressed: compressed.is_compressed(), - }) + SecretKey::from_secret_bytes(*bytes) + .map(|key| Self::from_inner(key, compressed)) .map_err(|_| EcdsaError::InvalidSecretKey) } /// Generate a new random secret key. + /// + /// Draws until the bytes land in `1..order`, which all but always happens + /// on the first draw; the order leaves under 2^-127 of the 32-byte range + /// out. pub fn generate(rng: &mut impl CryptoRng, compressed: Compression) -> Self { + loop { + let mut bytes = Zeroizing::new([0u8; ECDSA_SK_LEN]); + rng.fill_bytes(&mut *bytes); + + if let Ok(key) = SecretKey::from_secret_bytes(*bytes) { + return Self::from_inner(key, compressed); + } + } + } + + /// Pair a scalar with the public key it derives. + fn from_inner(inner: SecretKey, compressed: Compression) -> Self { Self { - inner: SigningKey::generate_from_rng(rng), + public: inner.public_key(), + inner, compressed: compressed.is_compressed(), } } @@ -227,62 +238,43 @@ impl EcdsaSecretKey { /// sum is refused rather than returned as one. pub fn add_tweak(&self, tweak: &[u8; ECDSA_SK_LEN]) -> Result { let scalar = tweak_scalar(tweak)?; - let sum = *self.inner.as_nonzero_scalar().as_ref() + scalar; - let sum = NonZeroScalar::new(sum).into_option().ok_or(EcdsaError::InvalidTweak)?; + let sum = self.inner.add_tweak(&scalar).map_err(|_| EcdsaError::InvalidTweak)?; - Ok(Self { - inner: SigningKey::from(sum), - compressed: self.compressed, - }) + Ok(Self::from_inner(sum, Compression::from(self.compressed))) } /// Negate the secret scalar in place. + /// + /// The stored public key is negated with it rather than rederived; mirroring + /// the point costs nothing next to a scalar multiplication. pub fn negate(&mut self) { - let neg = self.inner.as_nonzero_scalar().neg(); - self.inner = SigningKey::from(neg); + self.inner = self.inner.negate(); + self.public = self.public.negate(); } /// Derive the corresponding public key. pub fn public_key(&self) -> EcdsaPublicKey { - EcdsaPublicKey::from_inner(*self.inner.verifying_key(), Compression::from(self.compressed)) + EcdsaPublicKey::from_inner(self.public, Compression::from(self.compressed)) } /// Serialize to a 32-byte big-endian scalar. pub fn to_bytes(&self) -> Zeroizing<[u8; ECDSA_SK_LEN]> { - let mut fb = self.inner.to_bytes(); - let out = Zeroizing::new(fb.into()); - <[u8]>::zeroize(fb.as_mut()); - out + Zeroizing::new(self.inner.to_secret_bytes()) } /// Produce an ECDSA signature over a 32-byte prehashed message (RFC 6979, /// low-S normalised). - /// - /// # Errors - /// - /// Returns [`EcdsaError::SigningFailed`] if the underlying library rejects - /// the prehash. - pub fn sign(&self, msg_hash: &[u8; 32]) -> Result { - self - .inner - .sign_prehash(msg_hash) - .map(EcdsaSignature::from_inner) - .map_err(|_| EcdsaError::SigningFailed) + pub fn sign(&self, msg_hash: &[u8; 32]) -> EcdsaSignature { + EcdsaSignature::from_inner(self.inner.sign_ecdsa(Message::from_digest(*msg_hash))) } /// Sign and return a recoverable signature (RFC 6979, low-S normalised). /// Recovery embeds the key's compression flag in the signature. - /// - /// # Errors - /// - /// Returns [`EcdsaError::SigningFailed`] if the underlying library rejects - /// the prehash. - pub fn sign_recoverable(&self, msg_hash: &[u8; 32]) -> Result { - self - .inner - .sign_prehash(msg_hash) - .map(|(sig, rid)| EcdsaRecSignature::from_inner(sig, rid, Compression::from(self.compressed))) - .map_err(|_| EcdsaError::SigningFailed) + pub fn sign_recoverable(&self, msg_hash: &[u8; 32]) -> EcdsaRecSignature { + let rec = RecoverableSignature::sign_ecdsa_recoverable(Message::from_digest(*msg_hash), &self.inner); + let (rid, _) = rec.serialize_compact(); + + EcdsaRecSignature::from_inner(rec.to_standard(), rid, Compression::from(self.compressed)) } /// Verify that a public key matches this secret key. @@ -291,7 +283,27 @@ impl EcdsaSecretKey { /// different SEC1 form than this secret key's own preference still matches if /// it is the same point. pub fn verify_pubkey(&self, pubkey: &EcdsaPublicKey) -> bool { - self.inner.verifying_key() == pubkey.as_inner() + &self.public == pubkey.as_inner() + } +} + +impl Zeroize for EcdsaSecretKey { + /// Overwrites the scalar and the point it derives. + /// + /// The backend erases through a volatile write, which a plain assignment on + /// the drop path would be free to elide. Zero is no scalar, so the scalar + /// one is what it leaves, and the stored point follows it. + fn zeroize(&mut self) { + self.inner.non_secure_erase(); + self.public = self.inner.public_key(); + } +} + +impl ZeroizeOnDrop for EcdsaSecretKey {} + +impl Drop for EcdsaSecretKey { + fn drop(&mut self) { + self.zeroize(); } } @@ -321,8 +333,7 @@ type_cvrt!(TryFrom for EcdsaSecretKey, EcdsaError, |bytes| { #[cfg(test)] #[expect(clippy::ptr_arg, clippy::unwrap_used, reason = "test code")] mod tests { - use super::OID_PRIME_FIELD; - use crate::ecdsa::curve_consts::ORDER; + use crate::ecdsa::curve_consts::{GENERATOR, GENERATOR_COMPRESSED, OID_PRIME_FIELD, ORDER}; use crate::ecdsa::tests::*; use crate::ecdsa::{Compression, EcdsaError, EcdsaPublicKey, EcdsaSecretKey, ECDSA_SK_LEN}; use crate::prelude::*; @@ -436,12 +447,55 @@ mod tests { let corpus = Corpus::open(env!("CARGO_MANIFEST_DIR"), "ecdsa_sign"); for v in corpus.vectors::("sign_recoverable") { let sk = EcdsaSecretKey::from_bytes(&arr_from_hex(&v.sk), Compression::Compressed).unwrap(); - let sig = sk.sign_recoverable(&arr_from_hex::<32>(&v.msg)).unwrap(); + let sig = sk.sign_recoverable(&arr_from_hex::<32>(&v.msg)); assert_eq!(sig.to_compact(), arr_from_hex::<64>(&v.sig)); assert_eq!(sig.recovery_id(), v.recovery_id); } } + #[rstest] + fn the_generator_constant_matches_the_library() { + // The DER encoding names the generator, which the curve library does not + // expose; this is the check that the written-out constant is that point. + let one = EcdsaSecretKey::from_bytes( + &[0u8; 31] + .iter() + .chain(&[1u8]) + .copied() + .collect::>() + .try_into() + .unwrap(), + Compression::Compressed, + ) + .unwrap(); + + assert_eq!(one.public_key().to_uncompressed(), *GENERATOR); + assert_eq!(one.public_key().to_compressed(), GENERATOR_COMPRESSED); + } + + /// What the backend's erase leaves in place of the scalar. + const WIPED: [u8; ECDSA_SK_LEN] = [1u8; ECDSA_SK_LEN]; + + #[rstest] + fn zeroize_clears_the_scalar(alice_sk: EcdsaSecretKey) { + use zeroize::Zeroize; + + let mut sk = alice_sk; + let held = sk.public_key(); + sk.zeroize(); + + // Zero is not a valid scalar, so the wiped key holds one instead; what + // matters is that the original scalar is gone. + assert_ne!(*sk.to_bytes(), ALICE_SK); + assert_eq!(*sk.to_bytes(), WIPED); + + // The stored point follows the scalar, or a wiped key would go on + // vouching for the public key it used to hold. + assert_ne!(sk.public_key(), held); + assert!(sk.verify_pubkey(&sk.public_key())); + assert!(!sk.verify_pubkey(&held)); + } + #[rstest] fn from_bytes_roundtrip(alice_sk: EcdsaSecretKey) { let bytes = alice_sk.to_bytes(); @@ -518,21 +572,21 @@ mod tests { #[rstest] fn sign_is_deterministic(alice_sk: EcdsaSecretKey) { - let sig1 = alice_sk.sign(&MSG).unwrap(); - let sig2 = alice_sk.sign(&MSG).unwrap(); + let sig1 = alice_sk.sign(&MSG); + let sig2 = alice_sk.sign(&MSG); assert_eq!(sig1, sig2); } #[rstest] fn sign_recoverable_roundtrip(alice_sk: EcdsaSecretKey) { - let sig = alice_sk.sign_recoverable(&MSG).unwrap(); + let sig = alice_sk.sign_recoverable(&MSG); let recovered = EcdsaPublicKey::recover(&MSG, &sig).unwrap(); assert_eq!(recovered, alice_sk.public_key()); } #[rstest] fn sign_verify_roundtrip(alice_sk: EcdsaSecretKey) { - let sig = alice_sk.sign(&MSG).unwrap(); + let sig = alice_sk.sign(&MSG); assert!(alice_sk.public_key().verify(&MSG, &sig).is_ok()); } @@ -551,7 +605,7 @@ mod tests { #[rstest] fn verify_rejects_wrong_key(alice_sk: EcdsaSecretKey, bob_sk: EcdsaSecretKey) { assert!(!alice_sk.verify_pubkey(&bob_sk.public_key())); - let sig = alice_sk.sign(&MSG).unwrap(); + let sig = alice_sk.sign(&MSG); assert!(bob_sk.public_key().verify(&MSG, &sig).is_err()); } } diff --git a/pkgs/pkc/src/ecdsa/sig_ops.rs b/pkgs/pkc/src/ecdsa/sig_ops.rs index 54257ca4..ad207f22 100644 --- a/pkgs/pkc/src/ecdsa/sig_ops.rs +++ b/pkgs/pkc/src/ecdsa/sig_ops.rs @@ -17,8 +17,7 @@ use dash_types::dlgt_codec; use dash_types::type_cvrt; #[cfg(feature = "codec")] use dash_types::type_id::{TypeId, Unencodable}; -use k256::ecdsa::{DerSignature, Signature}; -use k256::elliptic_curve::scalar::IsHigh; +use secp256k1::ecdsa::{SerializedSignature, Signature}; use core::hash::{Hash, Hasher}; @@ -55,7 +54,7 @@ impl EcdsaSignature { /// Returns [`EcdsaError::InvalidSignature`] when `r` or `s` is zero or not /// a scalar below the curve order. pub fn from_bytes(bytes: &[u8; ECDSA_SIG_LEN]) -> Result { - Signature::from_slice(bytes) + Signature::from_compact(bytes) .map(Self) .map_err(|_| EcdsaError::InvalidSignature) } @@ -73,25 +72,34 @@ impl EcdsaSignature { } /// Whether the S component is in the lower half of the curve order. + /// + /// Asked by normalising a copy; the backend offers no query of its own, and + /// normalisation is a no-op exactly when S is already low. pub fn is_low_s(&self) -> bool { - !bool::from(self.0.s().is_high()) + self.normalized().is_none() } /// Return a signature with the S value normalised to the lower half of the /// curve order. Returns `None` if already normalised. pub fn normalize_s(&self) -> Option { - let normalized = self.0.normalize_s(); - (normalized != self.0).then_some(Self(normalized)) + self.normalized().map(Self) + } + + /// The low-S form of this signature, or `None` when it is already low. + fn normalized(&self) -> Option { + let mut sig = self.0; + sig.normalize_s(); + (sig != self.0).then_some(sig) } /// Emit the 64-byte layout (r || s). pub fn to_bytes(&self) -> [u8; ECDSA_SIG_LEN] { - self.0.to_bytes().into() + self.0.serialize_compact() } /// Encode as DER bytes. pub fn to_der(&self) -> EcdsaDerSig { - EcdsaDerSig(self.0.to_der()) + EcdsaDerSig(self.0.serialize_der()) } } @@ -110,22 +118,22 @@ impl AsRef for EcdsaSignature { /// DER-encoded ECDSA signature (variable length, typically 70-72 bytes). #[derive(Clone, Debug)] #[cfg_attr(feature = "codec", derive(Unencodable))] -pub struct EcdsaDerSig(DerSignature); +pub struct EcdsaDerSig(SerializedSignature); impl EcdsaDerSig { /// Raw DER bytes. pub fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() + self.0.as_ref() } /// Byte length. pub fn len(&self) -> usize { - self.0.as_bytes().len() + self.0.len() } /// Whether the DER encoding is empty (always false for valid signatures). pub fn is_empty(&self) -> bool { - self.0.as_bytes().is_empty() + self.len() == 0 } } diff --git a/pkgs/pkc/src/ecdsa/sig_rec_ops.rs b/pkgs/pkc/src/ecdsa/sig_rec_ops.rs index 00eb7e46..e7193b84 100644 --- a/pkgs/pkc/src/ecdsa/sig_rec_ops.rs +++ b/pkgs/pkc/src/ecdsa/sig_rec_ops.rs @@ -17,7 +17,7 @@ use dash_num::Hash256; use dash_types::type_cvrt; #[cfg(feature = "codec")] use dash_types::{dlgt_codec, type_id::TypeId}; -use k256::ecdsa::{RecoveryId, Signature}; +use secp256k1::ecdsa::{RecoveryId, Signature}; /// An ECDSA signature with recovery id and compression metadata. #[derive(Clone, Debug, Eq, Hash, PartialEq)] @@ -36,7 +36,7 @@ impl EcdsaRecSignature { pub(super) fn from_inner(inner: Signature, recovery_id: RecoveryId, compressed: Compression) -> Self { Self { sig: EcdsaSignature::from_inner(inner), - flags: CompactFlags::from_parts(recovery_id.to_byte(), compressed), + flags: CompactFlags::from_parts(recovery_id.to_u8(), compressed), } } @@ -75,11 +75,10 @@ impl EcdsaRecSignature { /// The recovery id in the form the backend expects. /// - /// Infallible, unlike [`RecoveryId::from_byte`]: `CompactFlags` encodes only - /// ids in `0..=3`, so both bits are in range by construction. + /// Infallible, like the masked constructor it uses; `CompactFlags` encodes + /// only ids in `0..=3`, so nothing is ever masked away. pub(super) const fn backend_recovery_id(&self) -> RecoveryId { - let id = self.flags.recovery_id(); - RecoveryId::new(id & 1 == 1, id & 2 == 2) + RecoveryId::from_u8_masked(self.flags.recovery_id()) } /// The plain signature without recovery metadata. @@ -134,7 +133,7 @@ mod tests { #[case(3)] fn backend_recovery_id_matches_byte(#[case] id: u8, alice_sig: EcdsaSignature) { let rec = EcdsaRecSignature::from_parts(alice_sig, id, Compression::Compressed).unwrap(); - assert_eq!(rec.backend_recovery_id().to_byte(), id); + assert_eq!(rec.backend_recovery_id().to_u8(), id); } #[rstest] diff --git a/pkgs/pkc/src/ecdsa/tests.rs b/pkgs/pkc/src/ecdsa/tests.rs index 3b8958f1..8a7501fc 100644 --- a/pkgs/pkc/src/ecdsa/tests.rs +++ b/pkgs/pkc/src/ecdsa/tests.rs @@ -55,10 +55,10 @@ pub fn bob_sk() -> EcdsaSecretKey { #[fixture] pub fn alice_rec_sig() -> EcdsaRecSignature { - alice_sk().sign_recoverable(&MSG).unwrap() + alice_sk().sign_recoverable(&MSG) } #[fixture] pub fn alice_sig() -> EcdsaSignature { - alice_sk().sign(&MSG).unwrap() + alice_sk().sign(&MSG) } From fdf99065510b641712152d9c5990c351f1a7bb56 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 15 Sep 2026 17:22:01 +0530 Subject: [PATCH 11/11] pkc%feat(ecdsa): grind the nonce for a low R --- pkgs/pkc/src/ecdsa/secret_ops.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index 90535ffa..248f44e5 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -27,7 +27,7 @@ use dash_types::{impl_stype, type_id::TypeId, ArrayBuf}; #[cfg(feature = "codec")] use dash_types::{Hashable, Numeric}; use rand_core::CryptoRng; -use secp256k1::ecdsa::RecoverableSignature; +use secp256k1::ecdsa::{sign_low_r, RecoverableSignature}; use secp256k1::{Message, PublicKey, Scalar, SecretKey}; use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; @@ -263,9 +263,12 @@ impl EcdsaSecretKey { } /// Produce an ECDSA signature over a 32-byte prehashed message (RFC 6979, - /// low-S normalised). + /// low-S normalised, low-R ground). + /// + /// [`sign_recoverable`](Self::sign_recoverable) is not ground; its form is + /// a fixed 64 bytes, with no prefix to save. pub fn sign(&self, msg_hash: &[u8; 32]) -> EcdsaSignature { - EcdsaSignature::from_inner(self.inner.sign_ecdsa(Message::from_digest(*msg_hash))) + EcdsaSignature::from_inner(sign_low_r(Message::from_digest(*msg_hash), &self.inner)) } /// Sign and return a recoverable signature (RFC 6979, low-S normalised). @@ -577,6 +580,21 @@ mod tests { assert_eq!(sig1, sig2); } + #[rstest] + fn sign_grinds_r_low(alice_sk: EcdsaSecretKey) { + // R lands low half the time on its own, so one signature proves nothing. + for i in 0..20 { + let sig = alice_sk.sign(&message_hash(i)); + + assert!(sig.to_bytes()[0] < 0x80, "R is not low for message {i}"); + assert!( + sig.to_der().len() <= 71, + "DER is {} bytes for message {i}", + sig.to_der().len() + ); + } + } + #[rstest] fn sign_recoverable_roundtrip(alice_sk: EcdsaSecretKey) { let sig = alice_sk.sign_recoverable(&MSG);