bsv_wasm/ecdsa/
sign.rs

1use crate::get_hash_digest;
2use crate::hash::sha256d_digest::Sha256d;
3use crate::BSVErrors;
4use crate::PrivateKey;
5use crate::RecoveryInfo;
6use crate::Signature;
7use crate::SigningHash;
8use crate::ECDSA;
9use digest::{Digest, FixedOutput};
10use ecdsa::hazmat::{rfc6979_generate_k, SignPrimitive};
11use ecdsa::RecoveryId;
12use elliptic_curve::ops::Reduce;
13use k256::ecdsa::recoverable;
14use k256::FieldBytes;
15use k256::U256;
16use k256::{ecdsa::Signature as SecpSignature, Scalar, SecretKey};
17use rand_core::OsRng;
18use rand_core::RngCore;
19use sha2::Sha256;
20#[cfg(target_arch = "wasm32")]
21use wasm_bindgen::prelude::*;
22#[cfg(target_arch = "wasm32")]
23use wasm_bindgen::throw_str;
24#[cfg(target_arch = "wasm32")]
25use wasm_bindgen::JsValue;
26
27impl ECDSA {
28    fn sign_preimage_deterministic_k<D>(priv_key: &SecretKey, digest: D, reverse_endian_k: bool) -> Result<(SecpSignature, Option<RecoveryId>), BSVErrors>
29    where
30        D: FixedOutput<OutputSize = digest::consts::U32> + digest::BlockInput + Clone + Default + digest::Reset + digest::Update + crate::ReversibleDigest,
31    {
32        let priv_scalar = priv_key.to_nonzero_scalar();
33        let final_digest = digest.finalize_fixed();
34        let k_digest = match reverse_endian_k {
35            true => <Scalar as Reduce<U256>>::from_le_bytes_reduced(final_digest),
36            false => <Scalar as Reduce<U256>>::from_be_bytes_reduced(final_digest),
37        };
38
39        let k = rfc6979_generate_k::<_, D>(&priv_scalar, &k_digest, &[]);
40
41        let msg_scalar = <Scalar as Reduce<U256>>::from_be_bytes_reduced(final_digest);
42        let (signature, recid) = priv_scalar.try_sign_prehashed(**k, msg_scalar)?;
43        let recoverable_id = recid.ok_or_else(ecdsa::Error::new)?.try_into()?;
44        let rec_sig = recoverable::Signature::new(&signature, recoverable_id)?;
45
46        let id = rec_sig.recovery_id();
47        let sig = SecpSignature::from(rec_sig);
48
49        Ok((sig, Some(id.into())))
50    }
51
52    fn sign_preimage_random_k(priv_key: &SecretKey, digest: &[u8], reverse_endian_k: bool, hash_algo: SigningHash) -> Result<(SecpSignature, Option<RecoveryId>), ecdsa::Error> {
53        let mut added_entropy = FieldBytes::default();
54        let rng = &mut OsRng;
55        rng.fill_bytes(&mut added_entropy);
56
57        let priv_scalar = priv_key.to_nonzero_scalar();
58        let k_digest = match reverse_endian_k {
59            true => {
60                let mut reversed_digest = digest.to_vec();
61                reversed_digest.reverse();
62
63                // TODO: Does this need to be from_be_slice ?
64                let scalar_uint = U256::from_le_slice(&reversed_digest);
65                Scalar::from_uint_reduced(scalar_uint)
66            }
67            false => Scalar::from_uint_reduced(U256::from_le_slice(digest)),
68        };
69
70        let k = match hash_algo {
71            SigningHash::Sha256 => **rfc6979_generate_k::<_, Sha256>(&priv_scalar, &k_digest, &added_entropy),
72            SigningHash::Sha256d => **rfc6979_generate_k::<_, Sha256d>(&priv_scalar, &k_digest, &added_entropy),
73        };
74
75        let msg_scalar = Scalar::from_uint_reduced(U256::from_le_slice(digest));
76        priv_scalar.try_sign_prehashed(k, msg_scalar)
77    }
78
79    /**
80     * Signs a message digest with a specific private and ephemeral key. I hope you know what you're doing!
81     */
82    pub(crate) fn sign_with_k_impl(private_key: &PrivateKey, ephemeral_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash) -> Result<Signature, BSVErrors> {
83        let priv_scalar = *private_key.secret_key.to_nonzero_scalar();
84        let k = *ephemeral_key.secret_key.to_nonzero_scalar();
85        let digest = get_hash_digest(hash_algo, preimage);
86        let digest_finalised = digest.finalize_fixed();
87        let msg_scalar = <Scalar as Reduce<U256>>::from_be_bytes_reduced(digest_finalised);
88        let (signature, recid) = priv_scalar.try_sign_prehashed(k, msg_scalar)?;
89        let recoverable_id = recid.ok_or_else(ecdsa::Error::new)?.try_into()?;
90        let rec_sig = recoverable::Signature::new(&signature, recoverable_id)?;
91        let recovery: Option<RecoveryId> = Some(rec_sig.recovery_id().into());
92        let sig = SecpSignature::from(rec_sig);
93        Ok(Signature {
94            sig,
95            recovery: recovery.map(|x| RecoveryInfo::new(x.is_y_odd(), x.is_x_reduced(), private_key.is_pub_key_compressed)),
96        })
97    }
98
99    /**
100     * Hashes the preimage with the specified Hashing algorithm and then signs the specified message.
101     * Secp256k1 signature inputs must be 32 bytes in length - SigningAlgo will output a 32 byte buffer.
102     * HASH+HMAC can be reversed for K generation if necessary.
103     */
104    pub(crate) fn sign_with_deterministic_k_impl(private_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash, reverse_k: bool) -> Result<Signature, BSVErrors> {
105        let digest = get_hash_digest(hash_algo, preimage);
106
107        let (sig, recovery) = ECDSA::sign_preimage_deterministic_k(&private_key.secret_key, digest, reverse_k)?;
108
109        Ok(Signature {
110            sig,
111            recovery: recovery.map(|x| RecoveryInfo::new(x.is_y_odd(), x.is_x_reduced(), private_key.is_pub_key_compressed)),
112        })
113    }
114
115    /**
116     * Hashes the preimage with the specified Hashing algorithm and then signs the specified message.
117     * Secp256k1 signature inputs must be 32 bytes in length - SigningAlgo will output a 32 byte buffer.
118     * HASH+HMAC can be reversed for K generation if necessary.
119     */
120    pub(crate) fn sign_with_random_k_impl(private_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash, reverse_k: bool) -> Result<Signature, BSVErrors> {
121        let digest = get_hash_digest(hash_algo, preimage);
122
123        let (sig, recovery) = ECDSA::sign_preimage_random_k(&private_key.secret_key, digest.finalize().as_slice(), reverse_k, hash_algo)?;
124
125        Ok(Signature {
126            sig,
127            recovery: recovery.map(|x| RecoveryInfo::new(x.is_y_odd(), x.is_x_reduced(), private_key.is_pub_key_compressed)),
128        })
129    }
130}
131
132#[cfg(target_arch = "wasm32")]
133#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-ecdsa"), wasm_bindgen)]
134impl ECDSA {
135    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-ecdsa"), wasm_bindgen(js_name = signWithRandomK))]
136    pub fn sign_with_random_k(private_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash, reverse_k: bool) -> Result<Signature, JsValue> {
137        match ECDSA::sign_with_random_k_impl(private_key, preimage, hash_algo, reverse_k) {
138            Ok(v) => Ok(v),
139            Err(e) => Err(JsValue::from_str(&e.to_string())),
140        }
141    }
142
143    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-ecdsa"), wasm_bindgen(js_name = sign))]
144    pub fn sign_with_deterministic_k(private_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash, reverse_k: bool) -> Result<Signature, JsValue> {
145        match ECDSA::sign_with_deterministic_k_impl(private_key, preimage, hash_algo, reverse_k) {
146            Ok(v) => Ok(v),
147            Err(e) => Err(JsValue::from_str(&e.to_string())),
148        }
149    }
150
151    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-ecdsa"), wasm_bindgen(js_name = signWithK))]
152    pub fn sign_with_k(private_key: &PrivateKey, ephemeral_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash) -> Result<Signature, JsValue> {
153        match ECDSA::sign_with_k_impl(private_key, ephemeral_key, preimage, hash_algo) {
154            Ok(v) => Ok(v),
155            Err(e) => throw_str(&e.to_string()),
156        }
157    }
158}
159
160#[cfg(not(target_arch = "wasm32"))]
161impl ECDSA {
162    pub fn sign_with_random_k(private_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash, reverse_k: bool) -> Result<Signature, BSVErrors> {
163        ECDSA::sign_with_random_k_impl(private_key, preimage, hash_algo, reverse_k)
164    }
165
166    pub fn sign_with_deterministic_k(private_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash, reverse_k: bool) -> Result<Signature, BSVErrors> {
167        ECDSA::sign_with_deterministic_k_impl(private_key, preimage, hash_algo, reverse_k)
168    }
169
170    pub fn sign_with_k(private_key: &PrivateKey, ephemeral_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash) -> Result<Signature, BSVErrors> {
171        ECDSA::sign_with_k_impl(private_key, ephemeral_key, preimage, hash_algo)
172    }
173}