ironfish_primitives/
legacy.rs

1//! Support for legacy transparent addresses and scripts.
2
3use byteorder::{ReadBytesExt, WriteBytesExt};
4use std::io::{self, Read, Write};
5use std::ops::Shl;
6
7use zcash_encoding::Vector;
8
9#[cfg(feature = "transparent-inputs")]
10pub mod keys;
11
12/// Minimal subset of script opcodes.
13enum OpCode {
14    // push value
15    PushData1 = 0x4c,
16    PushData2 = 0x4d,
17    PushData4 = 0x4e,
18
19    // stack ops
20    Dup = 0x76,
21
22    // bit logic
23    Equal = 0x87,
24    EqualVerify = 0x88,
25
26    // crypto
27    Hash160 = 0xa9,
28    CheckSig = 0xac,
29}
30
31/// A serialized script, used inside transparent inputs and outputs of a transaction.
32#[derive(Clone, Debug, Default, PartialEq)]
33pub struct Script(pub Vec<u8>);
34
35impl Script {
36    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
37        let script = Vector::read(&mut reader, |r| r.read_u8())?;
38        Ok(Script(script))
39    }
40
41    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
42        Vector::write(&mut writer, &self.0, |w, e| w.write_u8(*e))
43    }
44
45    /// Returns the address that this Script contains, if any.
46    pub fn address(&self) -> Option<TransparentAddress> {
47        if self.0.len() == 25
48            && self.0[0..3] == [OpCode::Dup as u8, OpCode::Hash160 as u8, 0x14]
49            && self.0[23..25] == [OpCode::EqualVerify as u8, OpCode::CheckSig as u8]
50        {
51            let mut hash = [0; 20];
52            hash.copy_from_slice(&self.0[3..23]);
53            Some(TransparentAddress::PublicKey(hash))
54        } else if self.0.len() == 23
55            && self.0[0..2] == [OpCode::Hash160 as u8, 0x14]
56            && self.0[22] == OpCode::Equal as u8
57        {
58            let mut hash = [0; 20];
59            hash.copy_from_slice(&self.0[2..22]);
60            Some(TransparentAddress::Script(hash))
61        } else {
62            None
63        }
64    }
65}
66
67impl Shl<OpCode> for Script {
68    type Output = Self;
69
70    fn shl(mut self, rhs: OpCode) -> Self {
71        self.0.push(rhs as u8);
72        self
73    }
74}
75
76impl Shl<&[u8]> for Script {
77    type Output = Self;
78
79    fn shl(mut self, data: &[u8]) -> Self {
80        if data.len() < OpCode::PushData1 as usize {
81            self.0.push(data.len() as u8);
82        } else if data.len() <= 0xff {
83            self.0.push(OpCode::PushData1 as u8);
84            self.0.push(data.len() as u8);
85        } else if data.len() <= 0xffff {
86            self.0.push(OpCode::PushData2 as u8);
87            self.0.extend(&(data.len() as u16).to_le_bytes());
88        } else {
89            self.0.push(OpCode::PushData4 as u8);
90            self.0.extend(&(data.len() as u32).to_le_bytes());
91        }
92        self.0.extend(data);
93        self
94    }
95}
96
97/// A transparent address corresponding to either a public key or a `Script`.
98#[derive(Debug, PartialEq, PartialOrd, Hash, Clone)]
99pub enum TransparentAddress {
100    PublicKey([u8; 20]), // TODO: Rename to PublicKeyHash
101    Script([u8; 20]),    // TODO: Rename to ScriptHash
102}
103
104impl TransparentAddress {
105    /// Generate the `scriptPubKey` corresponding to this address.
106    pub fn script(&self) -> Script {
107        match self {
108            TransparentAddress::PublicKey(key_id) => {
109                // P2PKH script
110                Script::default()
111                    << OpCode::Dup
112                    << OpCode::Hash160
113                    << &key_id[..]
114                    << OpCode::EqualVerify
115                    << OpCode::CheckSig
116            }
117            TransparentAddress::Script(script_id) => {
118                // P2SH script
119                Script::default() << OpCode::Hash160 << &script_id[..] << OpCode::Equal
120            }
121        }
122    }
123}
124
125#[cfg(any(test, feature = "test-dependencies"))]
126pub mod testing {
127    use proptest::prelude::{any, prop_compose};
128
129    use super::TransparentAddress;
130
131    prop_compose! {
132        pub fn arb_transparent_addr()(v in proptest::array::uniform20(any::<u8>())) -> TransparentAddress {
133            TransparentAddress::PublicKey(v)
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::{OpCode, Script, TransparentAddress};
141
142    #[test]
143    fn script_opcode() {
144        {
145            let script = Script::default() << OpCode::PushData1;
146            assert_eq!(&script.0, &[OpCode::PushData1 as u8]);
147        }
148    }
149
150    #[test]
151    fn script_pushdata() {
152        {
153            let script = Script::default() << &[1, 2, 3, 4][..];
154            assert_eq!(&script.0, &[4, 1, 2, 3, 4]);
155        }
156
157        {
158            let short_data = vec![2; 100];
159            let script = Script::default() << &short_data[..];
160            assert_eq!(script.0[0], OpCode::PushData1 as u8);
161            assert_eq!(script.0[1] as usize, 100);
162            assert_eq!(&script.0[2..], &short_data[..]);
163        }
164
165        {
166            let medium_data = vec![7; 1024];
167            let script = Script::default() << &medium_data[..];
168            assert_eq!(script.0[0], OpCode::PushData2 as u8);
169            assert_eq!(&script.0[1..3], &[0x00, 0x04][..]);
170            assert_eq!(&script.0[3..], &medium_data[..]);
171        }
172
173        {
174            let long_data = vec![42; 1_000_000];
175            let script = Script::default() << &long_data[..];
176            assert_eq!(script.0[0], OpCode::PushData4 as u8);
177            assert_eq!(&script.0[1..5], &[0x40, 0x42, 0x0f, 0x00][..]);
178            assert_eq!(&script.0[5..], &long_data[..]);
179        }
180    }
181
182    #[test]
183    fn p2pkh() {
184        let addr = TransparentAddress::PublicKey([4; 20]);
185        assert_eq!(
186            &addr.script().0,
187            &[
188                0x76, 0xa9, 0x14, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
189                0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x88, 0xac,
190            ]
191        );
192        assert_eq!(addr.script().address(), Some(addr));
193    }
194
195    #[test]
196    fn p2sh() {
197        let addr = TransparentAddress::Script([7; 20]);
198        assert_eq!(
199            &addr.script().0,
200            &[
201                0xa9, 0x14, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
202                0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x87,
203            ]
204        );
205        assert_eq!(addr.script().address(), Some(addr));
206    }
207}