1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
use crate::BSVErrors;
use crate::Hash;
use std::io::Write;

use crate::{P2PKHAddress, PrivateKey, Signature, SigningHash, VarInt, ECDSA};
use thiserror::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen::{throw_str, JsValue};

/**
 * Bitcoin Signed Message
 */
#[wasm_bindgen]
pub struct BSM {}

const MAGIC_BYTES: &[u8] = b"Bitcoin Signed Message:\n";

impl BSM {
    fn prepend_magic_bytes(msg: &[u8]) -> Result<Vec<u8>, BSVErrors> {
        let mut buffer: Vec<u8> = vec![];

        buffer.write_varint(MAGIC_BYTES.len() as u64)?;
        buffer.write_all(MAGIC_BYTES)?;
        buffer.write_varint(msg.len() as u64)?;
        buffer.write_all(msg)?;

        Ok(buffer)
    }

    /**
     * Sign a message with the intention of verifying with this same Address.
     * Used when using Bitcoin Signed Messages ()
     */
    pub(crate) fn sign_impl(priv_key: &PrivateKey, message: &[u8]) -> Result<Signature, BSVErrors> {
        let magic_message = BSM::prepend_magic_bytes(message)?;
        // let magic_message = message;
        ECDSA::sign_with_deterministic_k_impl(priv_key, &magic_message, SigningHash::Sha256d, false)
    }

    pub(crate) fn verify_message_impl(message: &[u8], signature: &Signature, address: &P2PKHAddress) -> Result<bool, BSVErrors> {
        let magic_message = BSM::prepend_magic_bytes(message)?;
        // let magic_message = message;

        let public_key = signature.get_public_key(&magic_message, SigningHash::Sha256d)?;
        let verify_p2pkh = P2PKHAddress::from_pubkey_impl(&public_key)?;

        let verify_address = verify_p2pkh.to_address_string_impl()?;
        let address_string = address.to_address_string_impl()?;
        if verify_address != address_string {
            return Err(BSVErrors::MessageVerification(format!(
                "Provided address ({}) does not match signature address ({})",
                address_string, verify_address
            )));
        }
        ECDSA::verify_digest_impl(&magic_message, &public_key, signature, SigningHash::Sha256d)?;
        Ok(true)
    }
}

#[wasm_bindgen]
impl BSM {
    /**
     * Sign a message with the intention of verifying with this same Address.
     * Used when using Bitcoin Signed Messages
     *
     * Returns boolean
     */
    #[wasm_bindgen(js_name = isValidMessage)]
    pub fn is_valid_message(message: &[u8], signature: &Signature, address: &P2PKHAddress) -> bool {
        BSM::verify_message_impl(message, signature, address).is_ok()
    }
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
impl BSM {
    #[wasm_bindgen(js_name = verifyMessage)]
    pub fn verify_message(message: &[u8], signature: &Signature, address: &P2PKHAddress) -> Result<bool, JsValue> {
        match BSM::verify_message_impl(message, signature, address) {
            Ok(v) => Ok(v),
            Err(e) => throw_str(&e.to_string()),
        }
    }

    #[wasm_bindgen(js_name = signMessage)]
    pub fn sign_message(priv_key: &PrivateKey, message: &[u8]) -> Result<Signature, JsValue> {
        match BSM::sign_impl(priv_key, message) {
            Ok(v) => Ok(v),
            Err(e) => throw_str(&e.to_string()),
        }
    }
}

#[cfg(not(target_arch = "wasm32"))]
impl BSM {
    pub fn verify_message(message: &[u8], signature: &Signature, address: &P2PKHAddress) -> Result<bool, BSVErrors> {
        BSM::verify_message_impl(message, signature, address)
    }

    pub fn sign_message(priv_key: &PrivateKey, message: &[u8]) -> Result<Signature, BSVErrors> {
        BSM::sign_impl(priv_key, message)
    }
}