-
Notifications
You must be signed in to change notification settings - Fork 255
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
Transaction builder #92
Changes from all commits
0161803
1862354
dab3c00
c26188a
a1cd9df
54ef63b
4b61120
532299d
17f6bbc
fa50d55
0ea4408
ab60b88
59ed258
7c07914
e666321
3a73f94
1760b27
a28d94f
4a6c9ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ fpe = "0.1" | |
hex = "0.3" | ||
lazy_static = "1" | ||
pairing = { path = "../pairing" } | ||
rand = "0.7" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a dependency on |
||
rand_core = "0.5" | ||
rand_os = "0.2" | ||
sapling-crypto = { path = "../sapling-crypto" } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
//! Support for legacy transparent addresses and scripts. | ||
|
||
use byteorder::{ReadBytesExt, WriteBytesExt}; | ||
use std::io::{self, Read, Write}; | ||
use std::ops::Shl; | ||
|
||
use crate::serialize::Vector; | ||
|
||
/// Minimal subset of script opcodes. | ||
enum OpCode { | ||
// push value | ||
PushData1 = 0x4c, | ||
PushData2 = 0x4d, | ||
PushData4 = 0x4e, | ||
|
||
// stack ops | ||
Dup = 0x76, | ||
|
||
// bit logic | ||
Equal = 0x87, | ||
EqualVerify = 0x88, | ||
|
||
// crypto | ||
Hash160 = 0xa9, | ||
CheckSig = 0xac, | ||
} | ||
|
||
/// A serialized script, used inside transparent inputs and outputs of a transaction. | ||
#[derive(Debug, Default)] | ||
pub struct Script(pub Vec<u8>); | ||
|
||
impl Script { | ||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> { | ||
let script = Vector::read(&mut reader, |r| r.read_u8())?; | ||
Ok(Script(script)) | ||
} | ||
|
||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> { | ||
Vector::write(&mut writer, &self.0, |w, e| w.write_u8(*e)) | ||
} | ||
} | ||
|
||
impl Shl<OpCode> for Script { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sucks air through teeth at repeating this C++ antipattern There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just use a method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to support concatenating different types with the same builder method (instead of a custom method per type), I would need to implement my own trait, which would result in more code than this. I also felt there was a benefit in having this legacy part be as close to the C++ code as possible for reviewability. |
||
type Output = Self; | ||
|
||
fn shl(mut self, rhs: OpCode) -> Self { | ||
self.0.push(rhs as u8); | ||
self | ||
} | ||
} | ||
|
||
impl Shl<&[u8]> for Script { | ||
type Output = Self; | ||
|
||
fn shl(mut self, data: &[u8]) -> Self { | ||
if data.len() < OpCode::PushData1 as usize { | ||
self.0.push(data.len() as u8); | ||
} else if data.len() <= 0xff { | ||
self.0.push(OpCode::PushData1 as u8); | ||
self.0.push(data.len() as u8); | ||
} else if data.len() <= 0xffff { | ||
self.0.push(OpCode::PushData2 as u8); | ||
self.0.extend(&(data.len() as u16).to_le_bytes()); | ||
} else { | ||
self.0.push(OpCode::PushData4 as u8); | ||
self.0.extend(&(data.len() as u32).to_le_bytes()); | ||
} | ||
self.0.extend(data); | ||
self | ||
} | ||
} | ||
|
||
/// A transparent address corresponding to either a public key or a `Script`. | ||
#[derive(Debug, PartialEq)] | ||
pub enum TransparentAddress { | ||
PublicKey([u8; 20]), | ||
Script([u8; 20]), | ||
} | ||
|
||
impl TransparentAddress { | ||
/// Generate the `scriptPubKey` corresponding to this address. | ||
pub fn script(&self) -> Script { | ||
match self { | ||
TransparentAddress::PublicKey(key_id) => { | ||
// P2PKH script | ||
Script::default() | ||
<< OpCode::Dup | ||
<< OpCode::Hash160 | ||
<< &key_id[..] | ||
<< OpCode::EqualVerify | ||
<< OpCode::CheckSig | ||
} | ||
TransparentAddress::Script(script_id) => { | ||
// P2SH script | ||
Script::default() << OpCode::Hash160 << &script_id[..] << OpCode::Equal | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::{OpCode, Script, TransparentAddress}; | ||
|
||
#[test] | ||
fn script_opcode() { | ||
{ | ||
let script = Script::default() << OpCode::PushData1; | ||
assert_eq!(&script.0, &[OpCode::PushData1 as u8]); | ||
} | ||
} | ||
|
||
#[test] | ||
fn script_pushdata() { | ||
{ | ||
let script = Script::default() << &[1, 2, 3, 4][..]; | ||
assert_eq!(&script.0, &[4, 1, 2, 3, 4]); | ||
} | ||
|
||
{ | ||
let short_data = vec![2; 100]; | ||
let script = Script::default() << &short_data[..]; | ||
assert_eq!(script.0[0], OpCode::PushData1 as u8); | ||
assert_eq!(script.0[1] as usize, 100); | ||
assert_eq!(&script.0[2..], &short_data[..]); | ||
} | ||
|
||
{ | ||
let medium_data = vec![7; 1024]; | ||
let script = Script::default() << &medium_data[..]; | ||
assert_eq!(script.0[0], OpCode::PushData2 as u8); | ||
assert_eq!(&script.0[1..3], &[0x00, 0x04][..]); | ||
assert_eq!(&script.0[3..], &medium_data[..]); | ||
} | ||
|
||
{ | ||
let long_data = vec![42; 1_000_000]; | ||
let script = Script::default() << &long_data[..]; | ||
assert_eq!(script.0[0], OpCode::PushData4 as u8); | ||
assert_eq!(&script.0[1..5], &[0x40, 0x42, 0x0f, 0x00][..]); | ||
assert_eq!(&script.0[5..], &long_data[..]); | ||
} | ||
} | ||
|
||
#[test] | ||
fn p2pkh() { | ||
let addr = TransparentAddress::PublicKey([4; 20]); | ||
assert_eq!( | ||
&addr.script().0, | ||
&[ | ||
0x76, 0xa9, 0x14, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, | ||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x88, 0xac, | ||
] | ||
) | ||
} | ||
|
||
#[test] | ||
fn p2sh() { | ||
let addr = TransparentAddress::Script([7; 20]); | ||
assert_eq!( | ||
&addr.script().0, | ||
&[ | ||
0xa9, 0x14, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, | ||
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x87, | ||
] | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please file a ticket about better error reporting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened #101.