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

Refactor and document utility functions #77

Merged
merged 6 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ name = "silentpayments"
crate-type = ["lib"]

[features]
default = ["sending", "receiving", "utils"]
default = ["sending", "receiving"]
sending = []
receiving = []
utils = []

[dependencies]
secp256k1 = {version = "0.28.1", features = ["rand"] }
Expand Down
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
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ mod error;
pub mod receiving;
#[cfg(feature = "sending")]
pub mod sending;
#[cfg(feature = "utils")]
pub mod utils;

pub use bitcoin_hashes;
Expand Down
7 changes: 7 additions & 0 deletions src/receiving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{

use crate::{
common::{calculate_P_n, calculate_t_n},
utils::hash::LabelHash,
Error, Result,
};
use bech32::ToBase32;
Expand All @@ -22,6 +23,12 @@ pub struct Label {
}

impl Label {
pub fn new(b_scan: SecretKey, m: u32) -> Label {
Label {
s: LabelHash::from_b_scan_and_m(b_scan, m).to_scalar(),
}
}

pub fn into_inner(self) -> Scalar {
self.s
}
Expand Down
263 changes: 1 addition & 262 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{receiving::Label, 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,261 +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
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)
}

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 struct InputsTag = hash_str("BIP0352/Inputs");

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

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

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

pub 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 struct SharedSecretHash(_);
}

impl InputsHash {
pub 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 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 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 fn to_label(self) -> Label {
// This is statistically extremely unlikely to panic.
let s = Scalar::from_be_bytes(self.to_byte_array())
.expect("hash value greater than curve order");
s.into()
}
}

impl SharedSecretHash {
pub 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 fn hash_outpoints(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
Loading