Skip to content

Commit

Permalink
SFT-UNKN: Add foundation-psbt crate.
Browse files Browse the repository at this point in the history
Signed-off-by: Jean-Pierre De Jesus DIAZ <[email protected]>
  • Loading branch information
jeandudey committed Oct 26, 2023
1 parent ad69da2 commit fe768bc
Show file tree
Hide file tree
Showing 18 changed files with 1,211 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"bip32/fuzz",
"codecs",
"ffi",
"psbt",
"test-vectors",
"ur",
"urtypes",
Expand All @@ -21,9 +22,11 @@ bech32 = { version = "0.9", default-features = false }
bip39 = { version = "2", default-features = false }
bitcoin = { version = "0.30", default-features = false }
bitcoin_hashes = { version = "0.13", default-features = false }
bitflags = { version = "2", default-features = false }
bs58 = "0.5"
crc = "3"
criterion = { version = "0.4" }
either = { version = "1", default-features = false }
foundation-arena = { path = "arena" }
foundation-bip32 = { path = "bip32" }
foundation-codecs = { path = "codecs" }
Expand All @@ -34,6 +37,8 @@ hex = { version = "0.4.2", default-features = false }
itertools = { version = "0.10", default-features = false }
libfuzzer-sys = "0.4"
minicbor = { version = "0.19.1", features = ["derive"] }
num-derive = "0.4"
num-traits = { version = "0.2", default-features = false }
nom = { version = "7", default-features = false }
phf = { version = "0.11", features = ["macros"], default-features = false }
rand_xoshiro = "0.6"
Expand Down
24 changes: 24 additions & 0 deletions psbt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later

[package]
name = "foundation-psbt"
version = "0.1.0"
edition = "2021"

[dependencies]
bitflags = { workspace = true }
bitcoin_hashes = { workspace = true }
either = { workspace = true }
foundation-bip32 = { workspace = true }
nom = { workspace = true }
num-derive = { workspace = true }
num-traits = { workspace = true }
secp256k1 = { workspace = true }

[dev-dependencies]
foundation-test-vectors = { workspace = true, features = ["psbt"] }

[features]
default = ["std"]
std = ["nom/std"]
53 changes: 53 additions & 0 deletions psbt/examples/dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

use foundation_psbt::parser::{psbt, global::TxModifiable};

use nom::error::VerboseError;

fn main() {
let mut args = std::env::args();
if args.len() != 2 {
eprintln!("Usage: {} <psbt-file>", args.nth(0).unwrap());
std::process::exit(1);
}

let file = std::env::args().nth(1).expect("PSBT file path");
println!("Reading `{file}'...\n");

let file = std::fs::read(file).expect("Failed to read PSBT file");
let mut parser = psbt::<_, VerboseError<_>>(|k, v| {
println!();
println!("{:?} {:?}", k, v);
});
let (_, psbt) = parser(&file).expect("Failed to parse PSBT file");

println!("Version: {}", psbt.version);
println!("Transaction version: {}", psbt.version);
println!(
"Fallback lock time: {}",
psbt.fallback_lock_time
.map(|t| t.to_string())
.unwrap_or("None".to_string())
);

let inputs_modifiable = psbt.tx_modifiable.contains(TxModifiable::INPUTS_MODIFIABLE);
let outputs_modifiable = psbt
.tx_modifiable
.contains(TxModifiable::OUTPUTS_MODIFIABLE);
let sighash_single = psbt.tx_modifiable.contains(TxModifiable::SIGHASH_SINGLE);

println!();
println!(
"Inputs modifiable? {}.",
if inputs_modifiable { "Yes" } else { "No" }
);
println!(
"Outputs modifiable? {}.",
if outputs_modifiable { "Yes" } else { "No" }
);
println!(
"Sighash single? {}.",
if sighash_single { "Yes" } else { "No" }
);
}
8 changes: 8 additions & 0 deletions psbt/src/hash_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

use bitcoin_hashes::{hash_newtype, sha256d};

hash_newtype! {
pub struct Txid(sha256d::Hash);
}
11 changes: 11 additions & 0 deletions psbt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

//! Partially Signed Bitcoin Transaction (PSBT) library.
#![cfg_attr(not(feature = "std"), no_std)]

pub mod hash_types;
pub mod parser;
pub mod taproot;
pub mod verifier;
86 changes: 86 additions & 0 deletions psbt/src/parser/compact_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

use nom::{
branch::alt,
bytes::complete::tag,
combinator::{cut, map, verify},
error::ParseError,
number::complete::{le_u16, le_u32, le_u64, u8},
sequence::preceded,
IResult,
};

/// Parse a Bitcoin protocol variable length integer.
///
/// # Errors
///
/// This function will return an error if the encoded variable length integer
/// is not canonical, for example, if the value is specifying a bigger
/// encoding than actually needed, like using 9 bytes to store a value that
/// fits in 1 byte.
pub fn compact_size<'a, Error: ParseError<&'a [u8]>>(
input: &'a [u8],
) -> IResult<&[u8], u64, Error> {
let parse_u8 = map(u8, u64::from);
let parse_u16 = preceded(
tag(b"\xFD"),
cut(verify(map(le_u16, u64::from), |&n| n > 0xFD)),
);
let parse_u32 = preceded(
tag(b"\xFE"),
cut(verify(map(le_u32, u64::from), |&n| n > 0xFFFF)),
);
let parse_u64 = preceded(
tag(b"\xFF"),
cut(verify(map(le_u64, u64::from), |&n| n > 0xFFFF_FFFF)),
);
let mut parser = alt((parse_u64, parse_u32, parse_u16, parse_u8));

parser(input)
}

#[cfg(test)]
mod tests {
use super::*;
use nom::error::Error;

#[test]
fn parse_compact_size() {
assert_eq!(
compact_size::<Error<_>>(&[0xFC]).unwrap(),
(&[] as &[u8], 0xFC)
);
assert_eq!(
compact_size::<Error<_>>(&[0xFD, 0xFF, 0xFF]).unwrap(),
(&[] as &[u8], 0xFFFF)
);
assert_eq!(
compact_size::<Error<_>>(&[0xFE, 0xFF, 0xFF, 0xFF, 0xFF]).unwrap(),
(&[] as &[u8], 0xFFFF_FFFF)
);
assert_eq!(
compact_size::<Error<_>>(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
.unwrap(),
(&[] as &[u8], 0xFFFF_FFFF_FFFF_FFFF)
);
}

#[test]
#[should_panic]
fn non_canonical_u16() {
compact_size::<Error<_>>(&[0xFD, 0xFC, 0x00]).unwrap();
}

#[test]
#[should_panic]
fn non_canonical_u32() {
compact_size::<Error<_>>(&[0xFE, 0xFF, 0xFF, 0x00, 0x00]).unwrap();
}

#[test]
#[should_panic]
fn non_canonical_u64() {
compact_size::<Error<_>>(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]).unwrap();
}
}
130 changes: 130 additions & 0 deletions psbt/src/parser/global.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

use bitflags::bitflags;
use nom::{
branch::alt,
combinator::{eof, map, verify},
error::{FromExternalError, ParseError},
sequence::terminated,
bytes::complete::tag,
multi::fold_many0,
number::complete::le_u32,
IResult,
};

use foundation_bip32::{
parser::{extended_public_key, key_source},
ExtendedPublicKey, KeySource,
};

use crate::parser::compact_size::compact_size;
use crate::parser::keypair::key_pair;
use crate::parser::transaction::{transaction, Transaction};

#[rustfmt::skip]
pub fn global_map<'a, F, Error>(mut f: F) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], GlobalMap, Error>
where
F: FnMut(ExtendedPublicKey<'a>, KeySource<'a>),
Error: ParseError<&'a [u8]>,
Error: FromExternalError<&'a [u8], secp256k1::Error>,
{
let keypairs = fold_many0(global_key_pair, GlobalMap::default, move |mut map, key_pair| {
match key_pair {
KeyPair::UnsignedTx(v) => map.transaction = Some(v),
KeyPair::Xpub { key, source } => f(key, source),
KeyPair::TxVersion(v) => map.transaction_version = Some(v),
KeyPair::FallbackLocktime(v) => map.fallback_locktime = Some(v),
KeyPair::InputCount(v) => map.input_count = Some(v),
KeyPair::OutputCount(v) => map.output_count = Some(v),
KeyPair::TxModifiable(v) => map.tx_modifiable = Some(v),
KeyPair::Version(v) => map.version = Some(v),
};

map
});

terminated(keypairs, tag(b"\x00"))
}

#[rustfmt::skip]
fn global_key_pair<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], KeyPair, Error>
where
Error: ParseError<&'a [u8]>,
Error: FromExternalError<&'a [u8], secp256k1::Error>,
{
let unsigned_tx = key_pair(0x00, eof, transaction);
let xpub = key_pair(0x01, extended_public_key, key_source);
let xpub = verify(xpub, |(k, v)| usize::from(k.depth) == v.path.len());
let tx_version = key_pair(0x02, eof, le_u32);
let fallback_locktime = key_pair(0x03, eof, le_u32);
let input_count = key_pair(0x04, eof, compact_size);
let output_count = key_pair(0x05, eof, compact_size);
let tx_modifiable = key_pair(0x06, eof, tx_modifiable);
let version = key_pair(0xFB, eof, le_u32);

let mut parser = alt((
map(unsigned_tx, |(_, v)| KeyPair::UnsignedTx(v)),
map(xpub, |(k, v)| KeyPair::Xpub { key: k, source: v }),
map(tx_version, |(_, v)| KeyPair::TxVersion(v)),
map(fallback_locktime, |(_, v)| KeyPair::FallbackLocktime(v)),
map(input_count, |(_, v)| KeyPair::InputCount(v)),
map(output_count, |(_, v)| KeyPair::OutputCount(v)),
map(tx_modifiable, |(_, v)| KeyPair::TxModifiable(v)),
map(version, |(_, v)| KeyPair::Version(v)),
));

parser(i)
}

fn tx_modifiable<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], TxModifiable, Error>
where
Error: ParseError<&'a [u8]>,
{
map(nom::number::complete::u8, TxModifiable::from_bits_retain)(i)
}

#[derive(Debug, Default)]
pub struct GlobalMap {
pub transaction: Option<Transaction>,
pub input_count: Option<u64>,
pub output_count: Option<u64>,
pub transaction_version: Option<u32>,
pub fallback_locktime: Option<u32>,
pub tx_modifiable: Option<TxModifiable>,
pub version: Option<u32>,
}

/// Entry type for the PSBT global map.
#[derive(Debug)]
enum KeyPair<'a> {
/// The unsigned transaction.
UnsignedTx(Transaction),
/// Extended public key entry.
Xpub {
/// The extended public key.
key: ExtendedPublicKey<'a>,
/// The key source information.
source: KeySource<'a>,
},
TxVersion(u32),
FallbackLocktime(u32),
InputCount(u64),
OutputCount(u64),
TxModifiable(TxModifiable),
Version(u32),
}

bitflags! {
/// Bit flags indicating which parts of the PSBT are modifiable.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TxModifiable: u8 {
/// Inputs of the PSBT are modifiable.
const INPUTS_MODIFIABLE = (1 << 0);
/// Outputs of the PSBT are modifiable.
const OUTPUTS_MODIFIABLE = (1 << 1);
/// Indicates that the transaction has a SIGHASH_SINGLE
/// signature who's input and output must be preserved.
const SIGHASH_SINGLE = (1 << 2);
}
}
Loading

0 comments on commit fe768bc

Please sign in to comment.