use k256::ecdsa::{SigningKey, signature::hazmat::PrehashSigner};
use sha2::{Sha256, Digest};
const SIGHASH_ALL_FORKID: u32 = 0x41;
pub fn compute_op_push_tx(
tx_hex: &str,
input_index: usize,
subscript: &str,
satoshis: i64,
) -> Result<(String, String), String> {
compute_op_push_tx_with_code_sep(tx_hex, input_index, subscript, satoshis, -1)
}
pub fn compute_op_push_tx_with_code_sep(
tx_hex: &str,
input_index: usize,
subscript: &str,
satoshis: i64,
code_separator_index: i64,
) -> Result<(String, String), String> {
let mut effective_subscript = subscript.to_string();
if code_separator_index >= 0 {
let trim_pos = ((code_separator_index as usize) + 1) * 2;
if trim_pos <= subscript.len() {
effective_subscript = subscript[trim_pos..].to_string();
}
}
let tx_bytes = hex_to_bytes(tx_hex)?;
let tx = parse_raw_tx(&tx_bytes)?;
if input_index >= tx.inputs.len() {
return Err(format!(
"compute_op_push_tx: input index {} out of range ({} inputs)",
input_index,
tx.inputs.len()
));
}
let subscript_bytes = hex_to_bytes(&effective_subscript)?;
let preimage = bip143_preimage(&tx, input_index, &subscript_bytes, satoshis as u64, SIGHASH_ALL_FORKID);
let sighash = sha256d(&preimage);
let mut key_bytes = [0u8; 32];
key_bytes[31] = 1;
let signing_key = SigningKey::from_slice(&key_bytes)
.map_err(|e| format!("compute_op_push_tx: create k=1 key: {}", e))?;
let (sig, _) = signing_key
.sign_prehash(&sighash)
.map_err(|e| format!("compute_op_push_tx: sign: {}", e))?;
let normalized = sig.normalize_s().unwrap_or(sig);
let der = normalized.to_der();
let mut result = der.as_bytes().to_vec();
result.push(SIGHASH_ALL_FORKID as u8);
let sig_hex = bytes_to_hex(&result);
let preimage_hex = bytes_to_hex(&preimage);
Ok((sig_hex, preimage_hex))
}
fn sha256d(data: &[u8]) -> [u8; 32] {
let first = Sha256::digest(data);
let second = Sha256::digest(first);
let mut out = [0u8; 32];
out.copy_from_slice(&second);
out
}
fn bip143_preimage(
tx: &ParsedTx,
input_index: usize,
subscript: &[u8],
satoshis: u64,
sig_hash_type: u32,
) -> Vec<u8> {
let mut prevouts_data = Vec::new();
for inp in &tx.inputs {
prevouts_data.extend_from_slice(&inp.prev_txid_bytes);
prevouts_data.extend_from_slice(&inp.prev_output_index.to_le_bytes());
}
let hash_prevouts = sha256d(&prevouts_data);
let mut sequence_data = Vec::new();
for inp in &tx.inputs {
sequence_data.extend_from_slice(&inp.sequence.to_le_bytes());
}
let hash_sequence = sha256d(&sequence_data);
let mut outputs_data = Vec::new();
for out in &tx.outputs {
outputs_data.extend_from_slice(&out.satoshis.to_le_bytes());
write_var_int(&mut outputs_data, out.script.len() as u64);
outputs_data.extend_from_slice(&out.script);
}
let hash_outputs = sha256d(&outputs_data);
let input = &tx.inputs[input_index];
let mut preimage = Vec::new();
preimage.extend_from_slice(&tx.version.to_le_bytes());
preimage.extend_from_slice(&hash_prevouts);
preimage.extend_from_slice(&hash_sequence);
preimage.extend_from_slice(&input.prev_txid_bytes);
preimage.extend_from_slice(&input.prev_output_index.to_le_bytes());
write_var_int(&mut preimage, subscript.len() as u64);
preimage.extend_from_slice(subscript);
preimage.extend_from_slice(&satoshis.to_le_bytes());
preimage.extend_from_slice(&input.sequence.to_le_bytes());
preimage.extend_from_slice(&hash_outputs);
preimage.extend_from_slice(&tx.locktime.to_le_bytes());
preimage.extend_from_slice(&sig_hash_type.to_le_bytes());
preimage
}
struct ParsedInput {
prev_txid_bytes: [u8; 32],
prev_output_index: u32,
sequence: u32,
}
struct ParsedOutput {
satoshis: u64,
script: Vec<u8>,
}
struct ParsedTx {
version: u32,
inputs: Vec<ParsedInput>,
outputs: Vec<ParsedOutput>,
locktime: u32,
}
fn parse_raw_tx(bytes: &[u8]) -> Result<ParsedTx, String> {
let mut offset = 0;
let read = |offset: &mut usize, n: usize| -> Result<&[u8], String> {
if *offset + n > bytes.len() {
return Err("compute_op_push_tx: transaction hex too short".to_string());
}
let slice = &bytes[*offset..*offset + n];
*offset += n;
Ok(slice)
};
let read_u32_le = |offset: &mut usize| -> Result<u32, String> {
let b = read(offset, 4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
};
let read_u64_le = |offset: &mut usize| -> Result<u64, String> {
let b = read(offset, 8)?;
Ok(u64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
};
let read_var_int = |offset: &mut usize| -> Result<u64, String> {
let first = read(offset, 1)?[0];
match first {
0..=0xfc => Ok(first as u64),
0xfd => {
let b = read(offset, 2)?;
Ok(u16::from_le_bytes([b[0], b[1]]) as u64)
}
0xfe => {
let b = read(offset, 4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as u64)
}
0xff => {
let b = read(offset, 8)?;
Ok(u64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
}
};
let version = read_u32_le(&mut offset)?;
let input_count = read_var_int(&mut offset)?;
let mut inputs = Vec::new();
for _ in 0..input_count {
let txid_slice = read(&mut offset, 32)?;
let mut prev_txid_bytes = [0u8; 32];
prev_txid_bytes.copy_from_slice(txid_slice);
let prev_output_index = read_u32_le(&mut offset)?;
let script_len = read_var_int(&mut offset)?;
let _ = read(&mut offset, script_len as usize)?;
let sequence = read_u32_le(&mut offset)?;
inputs.push(ParsedInput {
prev_txid_bytes,
prev_output_index,
sequence,
});
}
let output_count = read_var_int(&mut offset)?;
let mut outputs = Vec::new();
for _ in 0..output_count {
let satoshis = read_u64_le(&mut offset)?;
let script_len = read_var_int(&mut offset)?;
let script = read(&mut offset, script_len as usize)?.to_vec();
outputs.push(ParsedOutput { satoshis, script });
}
let locktime = read_u32_le(&mut offset)?;
Ok(ParsedTx {
version,
inputs,
outputs,
locktime,
})
}
fn write_var_int(buf: &mut Vec<u8>, n: u64) {
if n < 0xfd {
buf.push(n as u8);
} else if n <= 0xffff {
buf.push(0xfd);
buf.extend_from_slice(&(n as u16).to_le_bytes());
} else if n <= 0xffff_ffff {
buf.push(0xfe);
buf.extend_from_slice(&(n as u32).to_le_bytes());
} else {
buf.push(0xff);
buf.extend_from_slice(&n.to_le_bytes());
}
}
fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, String> {
if hex.len() % 2 != 0 {
return Err("odd-length hex string".to_string());
}
(0..hex.len())
.step_by(2)
.map(|i| {
u8::from_str_radix(&hex[i..i + 2], 16)
.map_err(|_| format!("invalid hex at position {}", i))
})
.collect()
}
fn bytes_to_hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}