Skip to content

Commit

Permalink
Move functions from utils.rs to utils/receiving.rs and utils/hash.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
cygnet3 committed Mar 15, 2024
1 parent cef42a6 commit dc36924
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 290 deletions.
2 changes: 1 addition & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::SharedSecretHash;
use crate::utils::hash::SharedSecretHash;
use crate::Result;
use bitcoin_hashes::Hash;
use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
Expand Down
2 changes: 1 addition & 1 deletion src/receiving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{

use crate::{
common::{calculate_P_n, calculate_t_n},
utils::LabelHash,
utils::hash::LabelHash,
Error, Result,
};
use bech32::ToBase32;
Expand Down
285 changes: 1 addition & 284 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::Error;
use bitcoin_hashes::{hash160, sha256t_hash_newtype, Hash, HashEngine};
use secp256k1::{Parity::Even, PublicKey, Scalar, SecretKey, XOnlyPublicKey};

pub(crate) mod hash;
pub mod receiving;
pub mod sending;

Expand All @@ -24,283 +21,3 @@ const OP_CHECKSIG: u8 = 0xAC;

// Only compressed pubkeys are supported for silent payments
const COMPRESSED_PUBKEY_SIZE: usize = 33;

// script templates for inputs allowed in BIP352 shared secret derivation
/// Check if a script_pub_key is taproot.
pub fn is_p2tr(spk: &[u8]) -> bool {
matches!(spk, [OP_1, OP_PUSHBYTES_32, ..] if spk.len() == 34)
}

fn is_p2wpkh(spk: &[u8]) -> bool {
matches!(spk, [OP_0, OP_PUSHBYTES_20, ..] if spk.len() == 22)
}

fn is_p2sh(spk: &[u8]) -> bool {
matches!(spk, [OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUAL] if spk.len() == 23)
}

fn is_p2pkh(spk: &[u8]) -> bool {
matches!(spk, [OP_DUP, OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUALVERIFY, OP_CHECKSIG] if spk.len() == 25)
}

/// Get the public keys from a set of input data.
///
/// # Arguments
///
/// * `script_sig` - The script signature as a byte array.
/// * `txinwitness` - The witness data.
/// * `script_pub_key` - The scriptpubkey from the output spent. This requires looking up the previous output.
///
/// # Returns
///
/// If no errors occur, this function will optionally return a PublicKey if this input is silent payment-eligible.
///
/// # Errors
///
/// This function will error if:
///
/// * The provided Vin data is incorrect.
pub fn get_pubkey_from_input(
script_sig: &[u8],
txinwitness: &Vec<Vec<u8>>,
script_pub_key: &[u8],
) -> Result<Option<PublicKey>, Error> {
if is_p2pkh(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(true, false) => {
let spk_hash = &script_pub_key[3..23];
for i in (COMPRESSED_PUBKEY_SIZE..=script_sig.len()).rev() {
if let Some(pubkey_bytes) = script_sig.get(i - COMPRESSED_PUBKEY_SIZE..i) {
let pubkey_hash = hash160::Hash::hash(pubkey_bytes);
if pubkey_hash.to_byte_array() == spk_hash {
return Ok(Some(PublicKey::from_slice(pubkey_bytes)?));
}
} else {
return Ok(None);
}
}
}
(_, true) => {
return Err(Error::InvalidVin(
"Empty script_sig for spending a p2pkh".to_owned(),
))
}
(false, _) => {
return Err(Error::InvalidVin(
"non empty witness for spending a p2pkh".to_owned(),
))
}
}
} else if is_p2sh(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(false, false) => {
let redeem_script = &script_sig[1..];
if is_p2wpkh(redeem_script) {
if let Some(value) = txinwitness.last() {
match (
PublicKey::from_slice(value),
value.len() == COMPRESSED_PUBKEY_SIZE,
) {
(Ok(pubkey), true) => {
return Ok(Some(pubkey));
}
(_, false) => {
return Ok(None);
}
// Not sure how we could get an error here, so just return none for now
// if the pubkey cant be parsed
(Err(_), _) => {
return Ok(None);
}
}
}
}
}
(_, true) => {
return Err(Error::InvalidVin(
"Empty script_sig for spending a p2sh".to_owned(),
))
}
(true, false) => return Ok(None),
}
} else if is_p2wpkh(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(false, true) => {
if let Some(value) = txinwitness.last() {
match (
PublicKey::from_slice(value),
value.len() == COMPRESSED_PUBKEY_SIZE,
) {
(Ok(pubkey), true) => {
return Ok(Some(pubkey));
}
(_, false) => {
return Ok(None);
}
// Not sure how we could get an error here, so just return none for now
// if the pubkey cant be parsed
(Err(_), _) => {
return Ok(None);
}
}
} else {
return Err(Error::InvalidVin("Empty witness".to_owned()));
}
}
(_, false) => {
return Err(Error::InvalidVin(
"Non empty script sig for spending a segwit output".to_owned(),
))
}
(true, _) => {
return Err(Error::InvalidVin(
"Empty witness for spending a segwit output".to_owned(),
))
}
}
} else if is_p2tr(script_pub_key) {
match (txinwitness.is_empty(), script_sig.is_empty()) {
(false, true) => {
// check for the optional annex
let annex = match txinwitness.last().and_then(|value| value.first()) {
Some(&0x50) => 1,
Some(_) => 0,
None => return Err(Error::InvalidVin("Empty or invalid witness".to_owned())),
};

// Check for script path
let stack_size = txinwitness.len();
if stack_size > annex && txinwitness[stack_size - annex - 1][1..33] == NUMS_H {
return Ok(None);
}

// Return the pubkey from the script pubkey
return XOnlyPublicKey::from_slice(&script_pub_key[2..34])
.map_err(Error::Secp256k1Error)
.map(|x_only_public_key| {
Some(PublicKey::from_x_only_public_key(x_only_public_key, Even))
});
}
(_, false) => {
return Err(Error::InvalidVin(
"Non empty script sig for spending a segwit output".to_owned(),
))
}
(true, _) => {
return Err(Error::InvalidVin(
"Empty witness for spending a segwit output".to_owned(),
))
}
}
}
Ok(None)
}

sha256t_hash_newtype! {
pub(crate) struct InputsTag = hash_str("BIP0352/Inputs");

/// BIP0352-tagged hash with tag \"Inputs\".
///
/// This is used for computing the inputs hash.
#[hash_newtype(forward)]
pub(crate) struct InputsHash(_);

pub(crate) struct LabelTag = hash_str("BIP0352/Label");

/// BIP0352-tagged hash with tag \"Label\".
///
/// This is used for computing the label tweak.
#[hash_newtype(forward)]
pub(crate) struct LabelHash(_);

pub(crate) struct SharedSecretTag = hash_str("BIP0352/SharedSecret");

/// BIP0352-tagged hash with tag \"SharedSecret\".
///
/// This hash type is for computing the shared secret.
#[hash_newtype(forward)]
pub(crate) struct SharedSecretHash(_);
}

impl InputsHash {
pub(crate) fn from_outpoint_and_A_sum(
smallest_outpoint: &[u8; 36],
A_sum: PublicKey,
) -> InputsHash {
let mut eng = InputsHash::engine();
eng.input(smallest_outpoint);
eng.input(&A_sum.serialize());
InputsHash::from_engine(eng)
}
pub(crate) fn to_scalar(self) -> Scalar {
// This is statistically extremely unlikely to panic.
Scalar::from_be_bytes(self.to_byte_array()).expect("hash value greater than curve order")
}
}

impl LabelHash {
pub(crate) fn from_b_scan_and_m(b_scan: SecretKey, m: u32) -> LabelHash {
let mut eng = LabelHash::engine();
eng.input(&b_scan.secret_bytes());
eng.input(&m.to_be_bytes());
LabelHash::from_engine(eng)
}

pub(crate) fn to_scalar(self) -> Scalar {
// This is statistically extremely unlikely to panic.
Scalar::from_be_bytes(self.to_byte_array()).expect("hash value greater than curve order")
}
}

impl SharedSecretHash {
pub(crate) fn from_ecdh_and_k(ecdh: &PublicKey, k: u32) -> SharedSecretHash {
let mut eng = SharedSecretHash::engine();
eng.input(&ecdh.serialize());
eng.input(&k.to_be_bytes());
SharedSecretHash::from_engine(eng)
}
}

pub(crate) fn calculate_input_hash(
outpoints_data: &[(String, u32)],
A_sum: PublicKey,
) -> Result<Scalar, Error> {
if outpoints_data.is_empty() {
return Err(Error::GenericError("No outpoints provided".to_owned()));
}

let mut outpoints: Vec<[u8; 36]> = Vec::with_capacity(outpoints_data.len());

// should probably just use an OutPoints type properly at some point
for (txid, vout) in outpoints_data {
let mut bytes: Vec<u8> = hex::decode(txid.as_str())?;

if bytes.len() != 32 {
return Err(Error::GenericError(format!(
"Invalid outpoint hex representation: {}",
txid
)));
}

// txid in string format is big endian and we need little endian
bytes.reverse();

let mut buffer = [0u8; 36];

buffer[..32].copy_from_slice(&bytes);
buffer[32..].copy_from_slice(&vout.to_le_bytes());
outpoints.push(buffer);
}

// sort outpoints
outpoints.sort_unstable();

if let Some(smallest_outpoint) = outpoints.first() {
Ok(InputsHash::from_outpoint_and_A_sum(smallest_outpoint, A_sum).to_scalar())
} else {
// This should never happen
Err(Error::GenericError(
"Unexpected empty outpoints vector".to_owned(),
))
}
}
Loading

0 comments on commit dc36924

Please sign in to comment.