Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #66 reverse_k signature digest #67

Merged
merged 17 commits into from
Dec 3, 2023
Merged
8 changes: 8 additions & 0 deletions packages/bsv-wasm/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,19 @@ impl ECDSA {
Ok(Signature(BSVECDSA::sign_with_deterministic_k(&private_key.0, preimage, hash_algo.into(), reverse_k)?))
}

pub fn sign_digest_with_deterministic_k(private_key: &PrivateKey, digest: &[u8]) -> Result<Signature, wasm_bindgen::JsError> {
Ok(Signature(BSVECDSA::sign_digest_with_deterministic_k(&private_key.0, digest)?))
}

pub fn sign_with_k(private_key: &PrivateKey, ephemeral_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash) -> Result<Signature, wasm_bindgen::JsError> {
Ok(Signature(BSVECDSA::sign_with_k(&private_key.0, &ephemeral_key.0, preimage, hash_algo.into())?))
}

pub fn verify_digest(message: &[u8], pub_key: &PublicKey, signature: &Signature, hash_algo: SigningHash) -> Result<bool, wasm_bindgen::JsError> {
Ok(BSVECDSA::verify_digest(message, &pub_key.0, &signature.0, hash_algo.into())?)
}

pub fn verify_hashbuf(digest: &[u8], pub_key: &PublicKey, signature: &Signature) -> Result<bool, wasm_bindgen::JsError> {
Ok(BSVECDSA::verify_hashbuf(digest, &pub_key.0, &signature.0)?)
}
}
4 changes: 4 additions & 0 deletions packages/bsv-wasm/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ impl Signature {
Ok(PublicKey(self.0.recover_public_key(message, hash_algo.into())?))
}

pub fn recover_public_key_from_digest(&self, digest: &[u8]) -> Result<PublicKey, wasm_bindgen::JsError> {
Ok(PublicKey(self.0.recover_public_key_from_digest(digest)?))
}

pub fn to_der_hex(&self) -> String {
BSVSignature::to_der_hex(&self.0)
}
Expand Down
4 changes: 4 additions & 0 deletions packages/bsv-wasm/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,8 @@ impl Transaction {
pub fn verify(&self, pub_key: &PublicKey, sig: &SighashSignature) -> bool {
self.0.verify(&pub_key.0, &sig.0)
}

pub fn _verify(&self, pub_key: &PublicKey, sig: &SighashSignature, reverse_digest: bool) -> bool {
self.0._verify(&pub_key.0, &sig.0, reverse_digest)
}
}
41 changes: 37 additions & 4 deletions src/ecdsa/sign.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use crate::get_hash_digest;
use crate::hash::sha256d_digest::Sha256d;
use crate::BSVErrors;
use crate::DigestBytes;
use crate::PrivateKey;
use crate::RecoveryInfo;
use crate::Sha256r;
use crate::Signature;
use crate::SigningHash;
use crate::ECDSA;
use digest::generic_array::GenericArray;
use digest::{Digest, FixedOutput};
use ecdsa::hazmat::{rfc6979_generate_k, SignPrimitive};
use ecdsa::RecoveryId;
use elliptic_curve::ops::Reduce;
use k256::ecdsa::recoverable;
use k256::FieldBytes;
use k256::U256;
use k256::{ecdsa::Signature as SecpSignature, Scalar, SecretKey};
use k256::ecdsa::{recoverable, Signature as SecpSignature};
use k256::{FieldBytes, Scalar, SecretKey, U256};
use rand_core::OsRng;
use rand_core::RngCore;
use sha2::Sha256;
Expand Down Expand Up @@ -43,6 +44,22 @@ impl ECDSA {
Ok((sig, Some(id.into())))
}

fn sign_digest_bytes_deterministic_k(priv_key: &SecretKey, digest_bytes: DigestBytes) -> Result<(SecpSignature, Option<RecoveryId>), BSVErrors> {
let priv_scalar = priv_key.to_nonzero_scalar();
let msg_scalar = <Scalar as Reduce<U256>>::from_be_bytes_reduced(digest_bytes);

let k = rfc6979_generate_k::<_, Sha256r>(&priv_scalar, &msg_scalar, &[]);

let (signature, recid) = priv_scalar.try_sign_prehashed(**k, msg_scalar)?;
let recoverable_id = recid.ok_or_else(ecdsa::Error::new)?.try_into()?;
let rec_sig = recoverable::Signature::new(&signature, recoverable_id)?;

let id = rec_sig.recovery_id();
let sig = SecpSignature::from(rec_sig);

Ok((sig, Some(id.into())))
}

fn sign_preimage_random_k(priv_key: &SecretKey, digest: &[u8], reverse_endian_k: bool, hash_algo: SigningHash) -> Result<(SecpSignature, Option<RecoveryId>), ecdsa::Error> {
let mut added_entropy = FieldBytes::default();
let rng = &mut OsRng;
Expand Down Expand Up @@ -90,6 +107,18 @@ impl ECDSA {
})
}

/**
* Signs the preimage hash digest directly. I hope you know what you're doing!
*/
pub(crate) fn sign_digest_with_deterministic_k_impl(private_key: &PrivateKey, digest: &DigestBytes) -> Result<Signature, BSVErrors> {
let (sig, recovery) = ECDSA::sign_digest_bytes_deterministic_k(&private_key.secret_key, *digest)?;

Ok(Signature {
sig,
recovery: recovery.map(|x| RecoveryInfo::new(x.is_y_odd(), x.is_x_reduced(), private_key.is_pub_key_compressed)),
})
}

/**
* Hashes the preimage with the specified Hashing algorithm and then signs the specified message.
* Secp256k1 signature inputs must be 32 bytes in length - SigningAlgo will output a 32 byte buffer.
Expand Down Expand Up @@ -135,4 +164,8 @@ impl ECDSA {
pub fn sign_with_k(private_key: &PrivateKey, ephemeral_key: &PrivateKey, preimage: &[u8], hash_algo: SigningHash) -> Result<Signature, BSVErrors> {
ECDSA::sign_with_k_impl(private_key, ephemeral_key, preimage, hash_algo)
}

pub fn sign_digest_with_deterministic_k(private_key: &PrivateKey, digest: &[u8]) -> Result<Signature, BSVErrors> {
ECDSA::sign_digest_with_deterministic_k_impl(private_key, GenericArray::from_slice(digest))
}
}
22 changes: 21 additions & 1 deletion src/ecdsa/verify.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use crate::BSVErrors;
use crate::DigestBytes;
use crate::Signature;
use crate::{get_hash_digest, PublicKey, SigningHash, ECDSA};
use digest::generic_array::GenericArray;
use ecdsa::hazmat::VerifyPrimitive;
use ecdsa::signature::DigestVerifier;
use k256::{ecdsa::VerifyingKey, EncodedPoint};
use elliptic_curve::ops::Reduce;
use elliptic_curve::sec1::FromEncodedPoint;
use elliptic_curve::AffinePoint;
use k256::Secp256k1;
use k256::{ecdsa::VerifyingKey, EncodedPoint, Scalar, U256};

impl ECDSA {
pub(crate) fn verify_digest_impl(message: &[u8], pub_key: &PublicKey, signature: &Signature, hash_algo: SigningHash) -> Result<bool, BSVErrors> {
Expand All @@ -13,10 +20,23 @@ impl ECDSA {
key.verify_digest(digest, &signature.sig)?;
Ok(true)
}

pub(crate) fn verify_hashbuf_impl(digest: DigestBytes, pub_key: &PublicKey, signature: &Signature) -> Result<bool, BSVErrors> {
let pub_key_bytes = pub_key.to_bytes_impl()?;
let z = <Scalar as Reduce<U256>>::from_be_bytes_reduced(digest);
let point = EncodedPoint::from_bytes(pub_key_bytes).map_err(|e| BSVErrors::CustomECDSAError(e.to_string()))?;
let key: AffinePoint<Secp256k1> = AffinePoint::<Secp256k1>::from_encoded_point(&point).unwrap();
key.verify_prehashed(z, &signature.sig)?;
Ok(true)
}
}

impl ECDSA {
pub fn verify_digest(message: &[u8], pub_key: &PublicKey, signature: &Signature, hash_algo: SigningHash) -> Result<bool, BSVErrors> {
ECDSA::verify_digest_impl(message, pub_key, signature, hash_algo)
}

pub fn verify_hashbuf(digest: &[u8], pub_key: &PublicKey, signature: &Signature) -> Result<bool, BSVErrors> {
ECDSA::verify_hashbuf_impl(*GenericArray::from_slice(digest), pub_key, signature)
}
}
17 changes: 10 additions & 7 deletions src/hash/digest_utils.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use digest::Digest;

use crate::{Sha256r, SigningHash};

use digest::consts::U32;
use digest::generic_array::GenericArray;
use digest::{Digest, FixedOutput};
// Reenable this when trait aliases become stable
// pub trait Digest32 = digest::FixedOutput<OutputSize = digest::consts::U32> + digest::BlockInput + Clone + Default + digest::Reset + digest::Update + crate::ReversibleDigest;

pub fn get_hash_digest(
hash_algo: SigningHash,
preimage: &[u8],
) -> impl digest::FixedOutput<OutputSize = digest::consts::U32> + digest::BlockInput + Clone + Default + digest::Reset + digest::Update + crate::ReversibleDigest {
pub trait HashDigest: FixedOutput<OutputSize = U32> + digest::BlockInput + Clone + Default + digest::Reset + digest::Update + crate::ReversibleDigest {}

impl HashDigest for Sha256r {}

pub type DigestBytes = GenericArray<u8, U32>;

pub fn get_hash_digest(hash_algo: SigningHash, preimage: &[u8]) -> impl HashDigest {
match hash_algo {
SigningHash::Sha256 => Digest::chain(Sha256r::default(), preimage),
SigningHash::Sha256d => Digest::chain(Sha256r::default(), Sha256r::digest(preimage)),
Expand Down
16 changes: 8 additions & 8 deletions src/interpreter/script_matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,21 +508,21 @@ impl Interpreter {
}
OpCodes::OP_CODESEPARATOR => state.codeseparator_offset = script_index + 1,
OpCodes::OP_CHECKSIG => {
let txscript = match tx {
let mut txscript = match tx {
Some(x) => x,
None => return Err(InterpreterError::RequiresTransaction(&OpCodes::OP_CHECKSIG)),
};

let is_signature_valid = checksig(state, txscript)?;
let is_signature_valid = checksig(state, &mut txscript)?;
state.stack.push_bool(is_signature_valid)?;
}
OpCodes::OP_CHECKSIGVERIFY => {
let txscript = match tx {
let mut txscript = match tx {
Some(x) => x,
None => return Err(InterpreterError::RequiresTransaction(&OpCodes::OP_CHECKSIGVERIFY)),
};

let is_signature_valid = checksig(state, txscript)?;
let is_signature_valid = checksig(state, &mut txscript)?;
Interpreter::verify(is_signature_valid)?
}
OpCodes::OP_CHECKMULTISIG => {
Expand Down Expand Up @@ -580,7 +580,7 @@ impl Interpreter {
}
}

fn checksig(state: &mut State, mut txscript: TxScript) -> Result<bool, InterpreterError> {
fn checksig(state: &mut State, txscript: &mut TxScript) -> Result<bool, InterpreterError> {
let public_key = state.stack.pop_bytes()?;
let signature = state.stack.pop_bytes()?;
let sighash_byte = signature.last().cloned();
Expand All @@ -589,8 +589,8 @@ fn checksig(state: &mut State, mut txscript: TxScript) -> Result<bool, Interpret
Some(x) => SigHash::try_from(x).map_err(|_| InterpreterError::FailedToConvertSighash)?,
None => return Err(InterpreterError::InvalidStackOperation("could not read Sighash flag from signature")),
};
let preimage = calculate_sighash_preimage(&mut txscript, sighash, state.codeseparator_offset)?;
let is_signature_valid = verify_tx_signature(&preimage, &mut txscript, &signature, &public_key)?;
let preimage = calculate_sighash_preimage(txscript, sighash, state.codeseparator_offset)?;
let is_signature_valid = verify_tx_signature(&preimage, txscript, &signature, &public_key)?;
Ok(is_signature_valid)
}

Expand Down Expand Up @@ -644,7 +644,7 @@ fn multisig(state: &mut State, txscript: &mut TxScript) -> Result<bool, Interpre

fn verify_tx_signature(preimage: &[u8], txscript: &mut TxScript, signature: &[u8], public_key: &[u8]) -> Result<bool, InterpreterError> {
let sighash_sig = SighashSignature::from_bytes_impl(signature, preimage)?;
let is_signature_valid = txscript.tx.verify(&PublicKey::from_bytes_impl(public_key)?, &sighash_sig);
let is_signature_valid = txscript.tx._verify(&PublicKey::from_bytes_impl(public_key)?, &sighash_sig, false) | txscript.tx._verify(&PublicKey::from_bytes_impl(public_key)?, &sighash_sig, true);
Ok(is_signature_valid)
}

Expand Down
2 changes: 1 addition & 1 deletion src/keypair/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ impl PrivateKey {
}

/**
* Standard ECDSA Message Signing using SHA256 as the digestg
* Standard ECDSA Message Signing using SHA256 as the digest.
*/
pub fn sign_message(&self, msg: &[u8]) -> Result<Signature, BSVErrors> {
PrivateKey::sign_message_impl(self, msg)
Expand Down
35 changes: 34 additions & 1 deletion src/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{get_hash_digest, BSVErrors, PublicKey, SigHash, SigningHash, ECDSA};
use digest::generic_array::GenericArray;
use k256::elliptic_curve::sec1::ToEncodedPoint;
use k256::{ecdsa::recoverable, ecdsa::Signature as SecpSignature, FieldBytes};
use num_traits::FromPrimitive;

Expand Down Expand Up @@ -76,7 +78,34 @@ impl Signature {
}
};

let pub_key = PublicKey::from_bytes_impl(&verify_key.to_bytes())?;
let pub_key = PublicKey::from_bytes(verify_key.to_encoded_point(recovery.is_pubkey_compressed).as_bytes())?;

Ok(pub_key)
}

pub fn get_public_key_from_digest(&self, digest: &[u8]) -> Result<PublicKey, BSVErrors> {
let recovery = match &self.recovery {
Some(v) => v,
None => {
return Err(BSVErrors::PublicKeyRecoveryError(
"No recovery info is provided in this signature, unable to recover private key. Use compact byte serialisation instead.".into(),
ecdsa::Error::new(),
))
}
};

let id = ecdsa::RecoveryId::new(recovery.is_y_odd, recovery.is_x_reduced);
let k256_recovery = id.try_into().map_err(|e| BSVErrors::PublicKeyRecoveryError("".into(), e))?;

let recoverable_sig = recoverable::Signature::new(&self.sig, k256_recovery)?;
let verify_key = match recoverable_sig.recover_verify_key_from_digest_bytes(GenericArray::from_slice(digest)) {
Ok(v) => v,
Err(e) => {
return Err(BSVErrors::PublicKeyRecoveryError(format!("Signature Hex: {} Id: {:?}", self.to_der_hex(), recovery), e));
}
};

let pub_key = PublicKey::from_bytes(verify_key.to_encoded_point(recovery.is_pubkey_compressed).as_bytes())?;

Ok(pub_key)
}
Expand Down Expand Up @@ -227,4 +256,8 @@ impl Signature {
pub fn recover_public_key(&self, message: &[u8], hash_algo: SigningHash) -> Result<PublicKey, BSVErrors> {
Signature::get_public_key(self, message, hash_algo)
}

pub fn recover_public_key_from_digest(&self, digest: &[u8]) -> Result<PublicKey, BSVErrors> {
Signature::get_public_key_from_digest(self, digest)
}
}
15 changes: 14 additions & 1 deletion src/transaction/sighash.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::get_hash_digest;
use crate::BSVErrors;
use crate::ReversibleDigest;
use crate::ECDSA;
use std::convert::TryFrom;
use std::io::Write;

use crate::{transaction::*, Hash, PrivateKey, PublicKey, Script, Signature};
use byteorder::{LittleEndian, WriteBytesExt};
use digest::FixedOutput;
use num_traits::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize};
use strum_macros::EnumString;
Expand Down Expand Up @@ -325,6 +328,15 @@ impl Transaction {
pub fn verify(&self, pub_key: &PublicKey, sig: &SighashSignature) -> bool {
ECDSA::verify_digest_impl(&sig.sighash_buffer, pub_key, &sig.signature, crate::SigningHash::Sha256d).unwrap_or(false)
}

pub fn _verify(&self, pub_key: &PublicKey, sig: &SighashSignature, reverse_k: bool) -> bool {
let digest = get_hash_digest(crate::SigningHash::Sha256d, &sig.sighash_buffer);
let hashbuf = match reverse_k {
true => digest.reverse().finalize_fixed(),
false => digest.finalize_fixed(),
};
ECDSA::verify_hashbuf_impl(hashbuf, pub_key, &sig.signature).unwrap_or(false)
}
}

impl Transaction {
Expand Down Expand Up @@ -364,7 +376,8 @@ impl SighashSignature {
}

pub(crate) fn from_bytes_impl(bytes: &[u8], sighash_buffer: &[u8]) -> Result<Self, BSVErrors> {
let signature = Signature::from_der_impl(&bytes[..bytes.len() - 1])?;
let der_bytes = if bytes.len() <= 72 { bytes } else { &bytes[..bytes.len() - 1] };
let signature = Signature::from_der_impl(der_bytes)?;
let sighash_type: SigHash = bytes
.last()
.cloned()
Expand Down
26 changes: 26 additions & 0 deletions tests/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,32 @@ mod tests {
assert_eq!(pub_key.to_hex().unwrap(), recovered_pub_key.to_hex().unwrap());
}

#[test]
fn recover_pub_key_from_signature_sha256d() {
let key = PrivateKey::from_wif("L4rGfRz3Q994Xns9wWti75K2CjxrCuzCqUAwN6yW7ia9nj4SDG32").unwrap();
let message = b"Hello";

let signature = ECDSA::sign_with_deterministic_k(&key, message, SigningHash::Sha256d, false).unwrap();

let pub_key = PublicKey::from_private_key(&key);
let recovered_pub_key = signature.recover_public_key(message, SigningHash::Sha256d).unwrap();

assert_eq!(pub_key.to_hex().unwrap(), recovered_pub_key.to_hex().unwrap());
}

#[test]
fn recover_pub_key_from_signature_sha256d_reverse_k() {
let key = PrivateKey::from_wif("L4rGfRz3Q994Xns9wWti75K2CjxrCuzCqUAwN6yW7ia9nj4SDG32").unwrap();
let message = b"Hello";

let signature = ECDSA::sign_with_deterministic_k(&key, message, SigningHash::Sha256d, true).unwrap();

let pub_key = PublicKey::from_private_key(&key);
let recovered_pub_key = signature.recover_public_key(message, SigningHash::Sha256d).unwrap();

assert_eq!(pub_key.to_hex().unwrap(), recovered_pub_key.to_hex().unwrap());
}

#[test]
fn to_compact_test() {
let key = PrivateKey::from_wif("L4rGfRz3Q994Xns9wWti75K2CjxrCuzCqUAwN6yW7ia9nj4SDG32").unwrap();
Expand Down
Loading