ark_core/
script.rs

1use bitcoin::opcodes::all::*;
2use bitcoin::taproot::TaprootSpendInfo;
3use bitcoin::ScriptBuf;
4use bitcoin::XOnlyPublicKey;
5use std::fmt;
6
7/// A conventional 2-of-2 multisignature [`ScriptBuf`].
8pub fn multisig_script(pk_0: XOnlyPublicKey, pk_1: XOnlyPublicKey) -> ScriptBuf {
9    ScriptBuf::builder()
10        .push_x_only_key(&pk_0)
11        .push_opcode(OP_CHECKSIGVERIFY)
12        .push_x_only_key(&pk_1)
13        .push_opcode(OP_CHECKSIG)
14        .into_script()
15}
16
17/// A [`ScriptBuf`] allowing the owner of `pk` to spend after `locktime_seconds` have passed from
18/// the time the corresponding output was included in a block.
19pub fn csv_sig_script(locktime: bitcoin::Sequence, pk: XOnlyPublicKey) -> ScriptBuf {
20    ScriptBuf::builder()
21        .push_int(locktime.to_consensus_u32() as i64)
22        .push_opcode(OP_CSV)
23        .push_opcode(OP_DROP)
24        .push_x_only_key(&pk)
25        .push_opcode(OP_CHECKSIG)
26        .into_script()
27}
28
29/// The script pubkey for the Taproot output corresponding to the given [`TaprootSpendInfo`].
30pub fn tr_script_pubkey(spend_info: &TaprootSpendInfo) -> ScriptBuf {
31    let output_key = spend_info.output_key();
32    let builder = bitcoin::blockdata::script::Builder::new();
33    builder
34        .push_opcode(OP_PUSHNUM_1)
35        .push_slice(output_key.serialize())
36        .into_script()
37}
38
39pub fn extract_sequence_from_csv_sig_script(
40    script: &ScriptBuf,
41) -> Result<bitcoin::Sequence, InvalidCsvSigScriptError> {
42    let csv_index = script
43        .to_bytes()
44        .windows(2)
45        .position(|window| *window == [OP_CSV.to_u8(), OP_DROP.to_u8()])
46        .ok_or(InvalidCsvSigScriptError)?;
47
48    let before_csv = &script.to_bytes()[..csv_index];
49
50    // It is either `OP_PUSHNUM_X` (a single byte) or `OP_PUSH_BYTES_X BYTES` (more than one
51    // byte).
52    let sequence = if before_csv.len() > 1 {
53        &before_csv[1..]
54    } else {
55        before_csv
56    };
57
58    let mut sequence = sequence.to_vec();
59    sequence.reverse();
60
61    let mut buffer = [0u8; 4];
62    let input_len = sequence.len();
63    let start_index = 4 - input_len; // calculate how many spaces to leave at the front
64
65    buffer[start_index..].copy_from_slice(&sequence);
66
67    let sequence = u32::from_be_bytes(buffer);
68
69    let sequence = bitcoin::Sequence::from_consensus(sequence);
70
71    Ok(sequence)
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct InvalidCsvSigScriptError;
76
77impl fmt::Display for InvalidCsvSigScriptError {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79        f.write_str("invalid CSV-Sig script")
80    }
81}
82
83impl std::error::Error for InvalidCsvSigScriptError {}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use bitcoin::locktime;
89    use bitcoin::XOnlyPublicKey;
90    use std::str::FromStr;
91
92    #[test]
93    fn test_extract_sequence_from_csv_sig_script() {
94        // Equivalent to two 512-second intervals.
95        let locktime_seconds = 1024;
96        let sequence = bitcoin::Sequence::from_seconds_ceil(locktime_seconds).unwrap();
97
98        let pk = XOnlyPublicKey::from_str(
99            "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
100        )
101        .unwrap();
102
103        let script = csv_sig_script(sequence, pk);
104
105        let parsed = extract_sequence_from_csv_sig_script(&script).unwrap();
106        let parsed = parsed.to_relative_lock_time();
107
108        assert_eq!(
109            parsed,
110            locktime::relative::LockTime::from_512_second_intervals(2).into()
111        );
112    }
113}