use zcash_encoding::CompactSize;
use crate::ZecError;
#[derive(Debug, Clone)]
pub struct AssembleSignedTxArgs {
pub unsigned_tx_bytes: Vec<u8>,
pub der_signature: Vec<u8>,
pub compressed_pubkey: [u8; 33],
}
pub fn assemble_signed_tx(args: AssembleSignedTxArgs) -> Result<Vec<u8>, ZecError> {
if args.der_signature.is_empty() {
return Err(ZecError::Encoding("empty DER signature".into()));
}
let push_sig_len = args.der_signature.len() + 1; if push_sig_len > 75 {
return Err(ZecError::Encoding(format!(
"scriptSig push too long for plain push opcode: {push_sig_len}"
)));
}
let mut script_sig: Vec<u8> = Vec::with_capacity(1 + push_sig_len + 1 + 33);
script_sig.push(push_sig_len as u8);
script_sig.extend_from_slice(&args.der_signature);
script_sig.push(0x01); script_sig.push(33u8);
script_sig.extend_from_slice(&args.compressed_pubkey);
splice_scriptsig_into_unsigned_v5(&args.unsigned_tx_bytes, &script_sig)
}
fn splice_scriptsig_into_unsigned_v5(
unsigned: &[u8],
script_sig: &[u8],
) -> Result<Vec<u8>, ZecError> {
const SCRIPTSIG_LEN_OFFSET: usize = 20 + 1 + 32 + 4;
if unsigned.len() <= SCRIPTSIG_LEN_OFFSET {
return Err(ZecError::Encoding(format!(
"unsigned tx truncated at scriptSig offset {SCRIPTSIG_LEN_OFFSET}"
)));
}
if unsigned[SCRIPTSIG_LEN_OFFSET] != 0x00 {
return Err(ZecError::Encoding(format!(
"expected empty scriptSig (compact-size 0) at offset {}, got {:#x}",
SCRIPTSIG_LEN_OFFSET, unsigned[SCRIPTSIG_LEN_OFFSET]
)));
}
let mut out = Vec::with_capacity(unsigned.len() + script_sig.len());
out.extend_from_slice(&unsigned[..SCRIPTSIG_LEN_OFFSET]);
CompactSize::write(&mut out, script_sig.len())
.map_err(|e| ZecError::Encoding(e.to_string()))?;
out.extend_from_slice(script_sig);
out.extend_from_slice(&unsigned[SCRIPTSIG_LEN_OFFSET + 1..]);
Ok(out)
}
pub fn low_s_normalize_der(der: &[u8]) -> Result<Vec<u8>, ZecError> {
use k256::ecdsa::Signature;
let sig = Signature::from_der(der)
.map_err(|e| ZecError::Encoding(format!("DER parse: {e}")))?;
let normalized = sig.normalize_s().unwrap_or(sig);
Ok(normalized.to_der().as_bytes().to_vec())
}