Skip to content

Commit

Permalink
Fix parsing of returned sigs in SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
afilini committed Jun 8, 2024
1 parent f97633c commit 7edbd53
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 3 deletions.
34 changes: 34 additions & 0 deletions model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,4 +1200,38 @@ mod tests {
let frag3 = MessageFragment::from([0x01u8, 0x10].as_slice());
assert!(message.push_fragment(frag3).is_err());
}

#[test]
fn test_psbt_deserialize() {
use bitcoin::consensus::encode::Decodable;

let data = vec![
112, 115, 98, 116, 255, 1, 0, 51, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 255,
255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255,
255, 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 34, 2, 2, 197, 26, 25, 133, 231,
108, 108, 49, 184, 176, 180, 62, 133, 4, 157, 245, 159, 189, 29, 23, 20, 245, 249, 92,
195, 15, 39, 118, 165, 58, 180, 64, 71, 48, 68, 2, 32, 119, 81, 57, 212, 66, 247, 161,
42, 202, 26, 32, 216, 164, 81, 159, 112, 126, 160, 193, 101, 255, 8, 152, 245, 80, 228,
241, 112, 209, 20, 129, 62, 2, 32, 64, 220, 9, 40, 22, 32, 245, 192, 179, 135, 67, 26,
117, 23, 58, 62, 51, 194, 187, 223, 137, 207, 254, 37, 192, 246, 97, 173, 47, 24, 224,
99, 1, 0, 34, 2, 3, 43, 100, 179, 66, 208, 104, 12, 78, 3, 153, 228, 105, 97, 172, 4,
47, 76, 145, 214, 124, 30, 246, 26, 115, 28, 125, 101, 62, 49, 114, 13, 207, 71, 48,
68, 2, 32, 2, 63, 160, 126, 130, 89, 120, 218, 154, 183, 199, 88, 109, 139, 14, 5, 44,
7, 85, 222, 160, 180, 35, 99, 245, 57, 64, 172, 183, 182, 208, 26, 2, 32, 108, 109,
206, 164, 78, 58, 53, 41, 6, 183, 130, 194, 160, 154, 43, 168, 150, 22, 91, 14, 189,
146, 52, 233, 153, 99, 193, 199, 0, 207, 213, 175, 1, 0, 34, 2, 2, 197, 26, 25, 133,
231, 108, 108, 49, 184, 176, 180, 62, 133, 4, 157, 245, 159, 189, 29, 23, 20, 245, 249,
92, 195, 15, 39, 118, 165, 58, 180, 64, 71, 48, 68, 2, 32, 37, 196, 20, 141, 57, 241,
174, 62, 78, 83, 101, 138, 129, 176, 13, 39, 145, 224, 220, 221, 73, 26, 94, 154, 87,
113, 165, 212, 221, 29, 66, 182, 2, 32, 92, 110, 93, 166, 236, 251, 226, 235, 224, 155,
28, 218, 184, 24, 19, 121, 187, 252, 174, 227, 165, 72, 57, 250, 22, 248, 13, 142, 242,
21, 74, 178, 1, 0,
];
println!("{:02X?}", data);
let psbt: bitcoin::util::psbt::PartiallySignedTransaction =
dbg!(bitcoin::consensus::encode::deserialize(&data)).unwrap();
}
}
12 changes: 9 additions & 3 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use model::{
};

mod inner_logic;
mod psbt;

pub const MAX_READ_FRAME: usize = 16;

Expand Down Expand Up @@ -272,9 +273,14 @@ impl PortalSdk {

let psbt = send_with_retry!(self.requests, Request::SignPsbt(psbt.clone().into()), Ok(Reply::SignedPsbt(s)) => break Ok(s))?;

let mut psbt: model::bitcoin::util::psbt::Psbt =
deserialize(psbt.deref()).map_err(|_| SdkError::CommunicationError)?;
psbt.unsigned_tx = original_psbt.unsigned_tx.clone();
// We encode the signatures in a format that's almost psbt but incompatible in some cases,
// so we parse it manually here
let inputs =
psbt::PortalPsbt::parse(psbt.deref()).map_err(|_| SdkError::DeserializationError)?;
let mut psbt =
model::bitcoin::util::psbt::Psbt::from_unsigned_tx(original_psbt.unsigned_tx.clone())
.expect("Valid unsigned tx");
psbt.inputs = inputs.inputs;

original_psbt
.combine(psbt)
Expand Down
163 changes: 163 additions & 0 deletions sdk/src/psbt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use std::collections::HashMap;

use model::bitcoin::consensus::Decodable;
use model::bitcoin::util::psbt;

#[derive(Debug)]
pub struct PortalPsbt {
pub inputs: Vec<psbt::Input>,
}

impl PortalPsbt {
pub fn parse(data: &[u8]) -> Result<Self, ParseError> {
if &data[..5] != &[0x70, 0x73, 0x62, 0x74, 0xFF] {
return Err(ParseError::InvalidMagic);
}
let data = &data[5..];

let (_global, data) = RawMap::parse(data)?;
let mut inputs = vec![];

let mut data = data;
loop {
let (input_map, remainder) = RawMap::parse(data)?;
if input_map.0.is_empty() {
break;
}

inputs.push(input_map.into_psbt_input()?);

data = remainder;
}

Ok(PortalPsbt { inputs })
}
}

#[derive(Debug)]
pub struct RawMap(HashMap<(u64, Vec<u8>), Vec<u8>>);

impl RawMap {
pub fn parse(mut data: &[u8]) -> Result<(Self, &[u8]), ParseError> {
let mut map = HashMap::new();

while !data.is_empty() {
let mut cursor = std::io::Cursor::new(data);

let key = Vec::<u8>::consensus_decode(&mut cursor)?;
if key.is_empty() {
data = &data[1..];
break;
}
let key_type = model::bitcoin::VarInt::consensus_decode(&mut key.as_slice())?;

let value = Vec::<u8>::consensus_decode(&mut cursor)?;
let key = key.into_iter().skip(key_type.len()).collect();

map.insert((key_type.0, key), value);
data = &data[cursor.position() as usize..];
}

Ok((RawMap(map), data))
}

pub fn into_psbt_input(self) -> Result<psbt::Input, ParseError> {
fn map_err<E>(_: E) -> ParseError {
ParseError::InvalidData
}

self.0
.into_iter()
.try_fold(psbt::Input::default(), |mut state, ((ty, key), data)| {
match ty {
// PSBT_IN_PARTIAL_SIG
0x02 => {
let pk = model::bitcoin::PublicKey::from_slice(&key).map_err(map_err)?;
let sig = model::bitcoin::EcdsaSig::from_slice(&data).map_err(map_err)?;

state.partial_sigs.insert(pk, sig);
}
// PSBT_IN_TAP_KEY_SIG
0x13 => {
let sig = model::bitcoin::SchnorrSig::from_slice(&data).map_err(map_err)?;

state.tap_key_sig = Some(sig);
}
// PSBT_IN_TAP_SCRIPT_SIG
0x14 => {
use model::bitcoin::hashes::Hash;

let pk = model::bitcoin::XOnlyPublicKey::from_slice(&key[..32])
.map_err(map_err)?;
let lh = model::bitcoin::util::taproot::TapLeafHash::from_slice(&key[32..])
.map_err(map_err)?;
let sig = model::bitcoin::SchnorrSig::from_slice(&data).map_err(map_err)?;

state.tap_script_sigs.insert((pk, lh), sig);
}

// Ignore unknown types
_ => {}
}

Ok(state)
})
}
}

#[derive(Debug, Clone)]
pub enum ParseError {
InvalidMagic,
InvalidData,
}

impl From<model::bitcoin::consensus::encode::Error> for ParseError {
fn from(_: model::bitcoin::consensus::encode::Error) -> Self {
ParseError::InvalidData
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_parse_multiple_inputs() {
let data = vec![
0x70, 0x73, 0x62, 0x74, 0xFF, 0x01, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x22, 0x02, 0x02, 0xC5, 0x1A, 0x19, 0x85, 0xE7, 0x6C, 0x6C,
0x31, 0xB8, 0xB0, 0xB4, 0x3E, 0x85, 0x04, 0x9D, 0xF5, 0x9F, 0xBD, 0x1D, 0x17, 0x14,
0xF5, 0xF9, 0x5C, 0xC3, 0x0F, 0x27, 0x76, 0xA5, 0x3A, 0xB4, 0x40, 0x47, 0x30, 0x44,
0x02, 0x20, 0x77, 0x51, 0x39, 0xD4, 0x42, 0xF7, 0xA1, 0x2A, 0xCA, 0x1A, 0x20, 0xD8,
0xA4, 0x51, 0x9F, 0x70, 0x7E, 0xA0, 0xC1, 0x65, 0xFF, 0x08, 0x98, 0xF5, 0x50, 0xE4,
0xF1, 0x70, 0xD1, 0x14, 0x81, 0x3E, 0x02, 0x20, 0x40, 0xDC, 0x09, 0x28, 0x16, 0x20,
0xF5, 0xC0, 0xB3, 0x87, 0x43, 0x1A, 0x75, 0x17, 0x3A, 0x3E, 0x33, 0xC2, 0xBB, 0xDF,
0x89, 0xCF, 0xFE, 0x25, 0xC0, 0xF6, 0x61, 0xAD, 0x2F, 0x18, 0xE0, 0x63, 0x01, 0x00,
0x22, 0x02, 0x03, 0x2B, 0x64, 0xB3, 0x42, 0xD0, 0x68, 0x0C, 0x4E, 0x03, 0x99, 0xE4,
0x69, 0x61, 0xAC, 0x04, 0x2F, 0x4C, 0x91, 0xD6, 0x7C, 0x1E, 0xF6, 0x1A, 0x73, 0x1C,
0x7D, 0x65, 0x3E, 0x31, 0x72, 0x0D, 0xCF, 0x47, 0x30, 0x44, 0x02, 0x20, 0x02, 0x3F,
0xA0, 0x7E, 0x82, 0x59, 0x78, 0xDA, 0x9A, 0xB7, 0xC7, 0x58, 0x6D, 0x8B, 0x0E, 0x05,
0x2C, 0x07, 0x55, 0xDE, 0xA0, 0xB4, 0x23, 0x63, 0xF5, 0x39, 0x40, 0xAC, 0xB7, 0xB6,
0xD0, 0x1A, 0x02, 0x20, 0x6C, 0x6D, 0xCE, 0xA4, 0x4E, 0x3A, 0x35, 0x29, 0x06, 0xB7,
0x82, 0xC2, 0xA0, 0x9A, 0x2B, 0xA8, 0x96, 0x16, 0x5B, 0x0E, 0xBD, 0x92, 0x34, 0xE9,
0x99, 0x63, 0xC1, 0xC7, 0x00, 0xCF, 0xD5, 0xAF, 0x01, 0x00, 0x22, 0x02, 0x02, 0xC5,
0x1A, 0x19, 0x85, 0xE7, 0x6C, 0x6C, 0x31, 0xB8, 0xB0, 0xB4, 0x3E, 0x85, 0x04, 0x9D,
0xF5, 0x9F, 0xBD, 0x1D, 0x17, 0x14, 0xF5, 0xF9, 0x5C, 0xC3, 0x0F, 0x27, 0x76, 0xA5,
0x3A, 0xB4, 0x40, 0x47, 0x30, 0x44, 0x02, 0x20, 0x25, 0xC4, 0x14, 0x8D, 0x39, 0xF1,
0xAE, 0x3E, 0x4E, 0x53, 0x65, 0x8A, 0x81, 0xB0, 0x0D, 0x27, 0x91, 0xE0, 0xDC, 0xDD,
0x49, 0x1A, 0x5E, 0x9A, 0x57, 0x71, 0xA5, 0xD4, 0xDD, 0x1D, 0x42, 0xB6, 0x02, 0x20,
0x5C, 0x6E, 0x5D, 0xA6, 0xEC, 0xFB, 0xE2, 0xEB, 0xE0, 0x9B, 0x1C, 0xDA, 0xB8, 0x18,
0x13, 0x79, 0xBB, 0xFC, 0xAE, 0xE3, 0xA5, 0x48, 0x39, 0xFA, 0x16, 0xF8, 0x0D, 0x8E,
0xF2, 0x15, 0x4A, 0xB2, 0x01, 0x00,
];
let parsed = PortalPsbt::parse(&data).unwrap().inputs;

assert_eq!(parsed.len(), 3);
for input in &parsed {
assert_eq!(input.partial_sigs.len(), 1);
}
}
}

0 comments on commit 7edbd53

Please sign in to comment.