diff --git a/Cargo.toml b/Cargo.toml index 6b92e24..04b253f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "bip32/fuzz", "codecs", "ffi", + "psbt", "test-vectors", "ur", "urtypes", @@ -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" } @@ -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" diff --git a/psbt/Cargo.toml b/psbt/Cargo.toml new file mode 100644 index 0000000..ea32ace --- /dev/null +++ b/psbt/Cargo.toml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +# 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"] diff --git a/psbt/examples/dump.rs b/psbt/examples/dump.rs new file mode 100644 index 0000000..f215415 --- /dev/null +++ b/psbt/examples/dump.rs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// 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: {} ", 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" } + ); +} diff --git a/psbt/src/hash_types.rs b/psbt/src/hash_types.rs new file mode 100644 index 0000000..151cd52 --- /dev/null +++ b/psbt/src/hash_types.rs @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +use bitcoin_hashes::{hash_newtype, sha256d}; + +hash_newtype! { + pub struct Txid(sha256d::Hash); +} diff --git a/psbt/src/lib.rs b/psbt/src/lib.rs new file mode 100644 index 0000000..65afe68 --- /dev/null +++ b/psbt/src/lib.rs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// 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; diff --git a/psbt/src/parser/compact_size.rs b/psbt/src/parser/compact_size.rs new file mode 100644 index 0000000..eb5f24b --- /dev/null +++ b/psbt/src/parser/compact_size.rs @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// 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::>(&[0xFC]).unwrap(), + (&[] as &[u8], 0xFC) + ); + assert_eq!( + compact_size::>(&[0xFD, 0xFF, 0xFF]).unwrap(), + (&[] as &[u8], 0xFFFF) + ); + assert_eq!( + compact_size::>(&[0xFE, 0xFF, 0xFF, 0xFF, 0xFF]).unwrap(), + (&[] as &[u8], 0xFFFF_FFFF) + ); + assert_eq!( + compact_size::>(&[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::>(&[0xFD, 0xFC, 0x00]).unwrap(); + } + + #[test] + #[should_panic] + fn non_canonical_u32() { + compact_size::>(&[0xFE, 0xFF, 0xFF, 0x00, 0x00]).unwrap(); + } + + #[test] + #[should_panic] + fn non_canonical_u64() { + compact_size::>(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]).unwrap(); + } +} diff --git a/psbt/src/parser/global.rs b/psbt/src/parser/global.rs new file mode 100644 index 0000000..c9ff86a --- /dev/null +++ b/psbt/src/parser/global.rs @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// 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, + pub input_count: Option, + pub output_count: Option, + pub transaction_version: Option, + pub fallback_locktime: Option, + pub tx_modifiable: Option, + pub version: Option, +} + +/// 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); + } +} diff --git a/psbt/src/parser/hash.rs b/psbt/src/parser/hash.rs new file mode 100644 index 0000000..56b8db2 --- /dev/null +++ b/psbt/src/parser/hash.rs @@ -0,0 +1,70 @@ +use crate::{hash_types, taproot}; +use nom::{ + bytes::complete::take, + combinator::map_res, + error::{FromExternalError, ParseError}, + IResult, +}; + +/// Parses a [`bitcoin_hashes::Hash`]. +pub fn hash<'a, Hash, Error>(i: &'a [u8]) -> IResult<&'a [u8], Hash, Error> +where + Hash: bitcoin_hashes::Hash, + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + map_res(take(Hash::LEN), Hash::from_slice)(i) +} + +/// Define an alias for a hash parser. +/// +/// This macro exists to reduce the boilerplate needed to defined an alias to the +/// generic [`hash`] function as Rust doesn't provide support for aliasing a +/// function. For example, one can dream of +/// `type ripemd160<'a, Error> = hash::<'a, ripemd160::Hash, Error>;` +macro_rules! define_hash_aliases { + ($($name:ident),* $(,)?) => { + $( + pub fn $name<'a, Error>( + i: &'a [u8], + ) -> IResult<&'a [u8], ::bitcoin_hashes::$name::Hash, Error> + where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, + { + hash::<'a, ::bitcoin_hashes::$name::Hash, Error>(i) + } + )* + }; +} + +define_hash_aliases! { + ripemd160, + sha256, + sha256d, + hash160, +} + +pub fn taproot_leaf_hash<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], taproot::LeafHash, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + hash::<'a, taproot::LeafHash, Error>(i) +} + +pub fn taproot_node_hash<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], taproot::TapNodeHash, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + hash::<'a, taproot::TapNodeHash, Error>(i) +} + +pub fn txid<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], hash_types::Txid, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + hash::<'a, hash_types::Txid, Error>(i) +} diff --git a/psbt/src/parser/input.rs b/psbt/src/parser/input.rs new file mode 100644 index 0000000..75cf1de --- /dev/null +++ b/psbt/src/parser/input.rs @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +use bitcoin_hashes::{hash160, ripemd160, sha256, sha256d}; + +use nom::branch::alt; +use nom::combinator::{eof, map, rest}; +use nom::error::{FromExternalError, ParseError}; +use nom::multi::{fold_many0, length_value}; +use nom::number::complete::le_u32; +use nom::sequence::{terminated, tuple}; +use nom::bytes::complete::tag; +use nom::IResult; + +use secp256k1::{schnorr, PublicKey, XOnlyPublicKey}; + +use foundation_bip32::{parser::key_source, KeySource}; + +use crate::hash_types::Txid; +use crate::parser::compact_size::compact_size; +use crate::parser::hash::{ + hash160, ripemd160, sha256, sha256d, taproot_leaf_hash, taproot_node_hash, txid, +}; +use crate::parser::keypair::key_pair; +use crate::parser::secp::{ecdsa_signature, public_key, schnorr_signature, x_only_public_key}; +use crate::parser::transaction::{transaction, Transaction}; +use crate::taproot::{TapNodeHash, TaprootScriptSignature}; + +#[rustfmt::skip] +pub fn input_map<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], InputMap<'a>, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + let keypairs = fold_many0( + input_key_pair, + InputMap::default, + |mut map, key_pair| { + match key_pair { + KeyPair::NonWitnessUtxo(v) => map.non_witness_utxo = Some(v), + KeyPair::WitnessUtxo(v) => map.witness_utxo = Some(v), + KeyPair::PartialSig(_) => (), // TODO + KeyPair::SighashType(v) => map.sighash_type = Some(v), + KeyPair::RedeemScript(v) => map.redeem_script = Some(v), + KeyPair::WitnessScript(v) => map.witness_script = Some(v), + KeyPair::Bip32Derivation(_, _) => (), // TODO + KeyPair::FinalScriptsig(v) => map.final_scriptsig = Some(v), + KeyPair::FinalScriptwitness(v) => map.final_scriptwitness = Some(v), + KeyPair::PorCommitment(v) => map.por_commitment = Some(v), + KeyPair::Ripemd160(_) => (), // TODO + KeyPair::Sha256(_) => (), // TODO + KeyPair::Hash160(_) => (), // TODO + KeyPair::Hash256(_) => (), // TODO + KeyPair::PreviousTxid(v) => map.previous_txid = Some(v), + KeyPair::OutputIndex(v) => map.output_index = Some(v), + KeyPair::Sequence(v) => map.sequence = Some(v), + KeyPair::RequiredTimeLocktime(v) => map.required_time_locktime = Some(v), + KeyPair::RequiredHeightLocktime(v) => map.required_height_locktime = Some(v), + KeyPair::TapKeySig(v) => map.tap_key_sig = Some(v), + KeyPair::TapScriptSig(_, _) => (), // TODO + KeyPair::TapLeafScript(_, _) => (), // TODO + KeyPair::TapBip32Derivation(_, _) => (), // TODO + KeyPair::TapInternalKey(v) => map.tap_internal_key = Some(v), + KeyPair::TapMerkleRoot(v) => map.tap_merkle_root = Some(v), + }; + + map + }, + ); + + terminated(keypairs, tag("\x00"))(i) +} + +#[rustfmt::skip] +fn input_key_pair<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], KeyPair, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + let non_witness_utxo = key_pair(0x00, eof, transaction); + let witness_utxo = key_pair(0x01, eof, witness_utxo); + let partial_sig = key_pair(0x02, public_key, ecdsa_signature); + let sighash_type = key_pair(0x03, eof, le_u32); + let redeem_script = key_pair(0x04, eof, rest); + let witness_script = key_pair(0x05, eof, rest); + let bip32_derivation = key_pair(0x06, public_key, key_source); + let final_scriptsig = key_pair(0x07, eof, rest); + let final_scriptwitness = key_pair(0x08, eof, rest); + let por_commitment = key_pair(0x09, eof, rest); + let ripemd160 = key_pair(0x0a, ripemd160, rest); + let sha256 = key_pair(0x0b, sha256, rest); + let hash160 = key_pair(0x0c, hash160, rest); + let hash256 = key_pair(0x0d, sha256d, rest); + let previous_txid = key_pair(0x0e, eof, txid); + let output_index = key_pair(0x0f, eof, le_u32); + let sequence = key_pair(0x10, eof, le_u32); + let required_time_locktime = key_pair(0x11, eof, le_u32); + let required_height_locktime = key_pair(0x12, eof, le_u32); + let tap_key_sig = key_pair(0x13, eof, schnorr_signature); + let tap_script_sig = key_pair(0x14, tap_script_sig, schnorr_signature); + let tap_leaf_script = key_pair(0x15, rest, rest); // TODO + let tap_bip32_derivation = key_pair(0x16, x_only_public_key, rest); // TODO + let tap_internal_key = key_pair(0x17, eof, x_only_public_key); + let tap_merkle_root = key_pair(0x18, eof, taproot_node_hash); + + alt(( + map(non_witness_utxo, |(_, v)| KeyPair::NonWitnessUtxo(v)), + map(witness_utxo, |(_, v)| KeyPair::WitnessUtxo(v)), + map(partial_sig, |(k, _)| KeyPair::PartialSig(k)), + map(sighash_type, |(_, v)| KeyPair::SighashType(v)), + map(redeem_script, |(_, v)| KeyPair::RedeemScript(v)), + map(witness_script, |(_, v)| KeyPair::WitnessScript(v)), + map(bip32_derivation, |(k, v)| KeyPair::Bip32Derivation(k, v)), + map(final_scriptsig, |(_, v)| KeyPair::FinalScriptsig(v)), + map(final_scriptwitness, |(_, v)| KeyPair::FinalScriptwitness(v)), + map(por_commitment, |(_, v)| KeyPair::PorCommitment(v)), + map(ripemd160, |(k, _)| KeyPair::Ripemd160(k)), // TODO + map(sha256, |(k, _)| KeyPair::Sha256(k)), // TODO + map(hash160, |(k, _)| KeyPair::Hash160(k)), // TODO + map(hash256, |(k, _)| KeyPair::Hash256(k)), // TODO + map(previous_txid, |(_, v)| KeyPair::PreviousTxid(v)), + map(output_index, |(_, v)| KeyPair::OutputIndex(v)), + map(sequence, |(_, v)| KeyPair::Sequence(v)), + map(required_time_locktime, |(_, v)| KeyPair::RequiredTimeLocktime(v)), + map(required_height_locktime, |(_, v)| KeyPair::RequiredHeightLocktime(v)), + map(tap_key_sig, |(_, v)| KeyPair::TapKeySig(v)), + // This nesting is needed because `Alt` can only handle tuples up to + // 21 elements. + alt(( + map(tap_script_sig, |(k, v)| KeyPair::TapScriptSig(k, v)), + map(tap_leaf_script, |(k, v)| KeyPair::TapLeafScript(k, v)), + map(tap_bip32_derivation, |(k, v)| KeyPair::TapBip32Derivation(k, v)), + map(tap_internal_key, |(_, v)| KeyPair::TapInternalKey(v)), + map(tap_merkle_root, |(_, v)| KeyPair::TapMerkleRoot(v)), + )), + ))(i) +} + +fn witness_utxo<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], WitnessUtxo<'a>, Error> +where + Error: ParseError<&'a [u8]>, +{ + let amount = compact_size; + let script_pubkey = length_value(compact_size, rest); + + map(tuple((amount, script_pubkey)), |(amount, script_pubkey)| { + WitnessUtxo { + amount, + script_pubkey, + } + })(i) +} + +fn tap_script_sig<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], TaprootScriptSignature, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + let fields = tuple((x_only_public_key, taproot_leaf_hash)); + let mut parser = map(fields, |(x_only_public_key, leaf_hash)| { + TaprootScriptSignature { + x_only_public_key, + leaf_hash, + } + }); + + parser(i) +} + +#[derive(Debug, Default)] +pub struct InputMap<'a> { + pub non_witness_utxo: Option, + pub witness_utxo: Option>, + pub sighash_type: Option, + pub redeem_script: Option<&'a [u8]>, + pub witness_script: Option<&'a [u8]>, + pub final_scriptsig: Option<&'a [u8]>, + pub final_scriptwitness: Option<&'a [u8]>, + pub por_commitment: Option<&'a [u8]>, + pub previous_txid: Option, + pub output_index: Option, + pub sequence: Option, + pub required_time_locktime: Option, + pub required_height_locktime: Option, + pub tap_key_sig: Option, + pub tap_internal_key: Option, + pub tap_merkle_root: Option, +} + +#[derive(Debug)] +enum KeyPair<'a> { + NonWitnessUtxo(Transaction), + WitnessUtxo(WitnessUtxo<'a>), + PartialSig(PublicKey), + SighashType(u32), + RedeemScript(&'a [u8]), + WitnessScript(&'a [u8]), + Bip32Derivation(PublicKey, KeySource<'a>), + FinalScriptsig(&'a [u8]), + FinalScriptwitness(&'a [u8]), + PorCommitment(&'a [u8]), + Ripemd160(ripemd160::Hash), + Sha256(sha256::Hash), + Hash160(hash160::Hash), + Hash256(sha256d::Hash), + PreviousTxid(Txid), + OutputIndex(u32), + Sequence(u32), + RequiredTimeLocktime(u32), + RequiredHeightLocktime(u32), + TapKeySig(schnorr::Signature), + TapScriptSig(TaprootScriptSignature, schnorr::Signature), + TapLeafScript(&'a [u8], &'a [u8]), + TapBip32Derivation(XOnlyPublicKey, &'a [u8]), + TapInternalKey(XOnlyPublicKey), + TapMerkleRoot(TapNodeHash), +} + +#[derive(Debug)] +pub struct WitnessUtxo<'a> { + pub amount: u64, + pub script_pubkey: &'a [u8], +} diff --git a/psbt/src/parser/keypair.rs b/psbt/src/parser/keypair.rs new file mode 100644 index 0000000..de0c216 --- /dev/null +++ b/psbt/src/parser/keypair.rs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +use nom::combinator::{map, verify}; +use nom::error::ParseError; +use nom::multi::length_value; +use nom::sequence::tuple; +use nom::{IResult, Parser}; + +use crate::parser::compact_size::compact_size; + +/// Parse a ``. +pub fn key_pair<'a, F, G, K, V, E>( + key_type: u64, + key_data: F, + value: G, +) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], (K, V), E> +where + F: Parser<&'a [u8], K, E>, + G: Parser<&'a [u8], V, E>, + E: ParseError<&'a [u8]>, +{ + let value = length_value(compact_size, value); + tuple((key(key_type, key_data), value)) +} + +/// Parse a ``. +pub fn key<'a, K, F, E>( + key_type: u64, + key_data: F, +) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], K, E> +where + F: Parser<&'a [u8], K, E>, + E: ParseError<&'a [u8]>, +{ + let key_type = verify(compact_size, move |&k| k == key_type); + let fields = tuple((key_type, key_data)); + map(length_value(compact_size, fields), |(_, o)| o) +} diff --git a/psbt/src/parser/mod.rs b/psbt/src/parser/mod.rs new file mode 100644 index 0000000..7a44033 --- /dev/null +++ b/psbt/src/parser/mod.rs @@ -0,0 +1,144 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +//! Parser combinators for parsing PSBTs. +//! +//! This library provides a PSBT parser for memory constrained devices by +//! using streaming parsers in order to not require memory allocations. +//! +//! The PSBT file will be parsed (and validated depending on the use case) +//! on the fly. +//! +//! The approach in this library may not be suitable for systems with lots +//! of memory as it is slower than usual, however for embedded devices it +//! may excel as it doesn't need the entire PSBT in memory, just the bits +//! needed. +//! +//! When more data is needed it can be simply read from a file. + +pub mod compact_size; +pub mod global; +pub mod hash; +pub mod input; +pub mod keypair; +pub mod multi; +pub mod output; +// NOTE: Can't name this secp256k1 or else we can't refer to +// secp256k1::Error :-). might be a bug with Rust. +pub mod secp; +pub mod transaction; + +use nom::{ + bytes::complete::tag, + error::{FromExternalError, ParseError, ErrorKind}, + IResult, + Err, +}; + +use foundation_bip32::{ExtendedPublicKey, KeySource}; + +/// Parse a Partially Signed Bitcoin Transaction (PSBT). +pub fn psbt<'a, G, Error>(g: G) -> impl FnMut(&'a [u8]) -> IResult<&[u8], Psbt, Error> +where + G: FnMut(ExtendedPublicKey<'a>, KeySource<'a>), + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, + Error: FromExternalError<&'a [u8], bitcoin_hashes::FromSliceError>, +{ + let magic = tag(b"psbt\xff"); + let mut global_map = global::global_map(g); + + move |i: &'a [u8]| { + let (i, _) = magic(i)?; + let (i, global_map) = global_map(i)?; + println!("{:?}", global_map); + let mut input = i.clone(); + + let input_count = global_map.input_count.unwrap_or(0); + let output_count = global_map.output_count.unwrap_or(0); + + for _ in 0..input_count { + let input_ = input.clone(); + + match input::input_map(input_) { + Ok((i, o)) => { + println!("{:?}", o); + input = i; + } + Err(Err::Error(e)) => { + return Err(Err::Error(Error::append(i, ErrorKind::Count, e))) + } + Err(e) => return Err(e), + } + } + + for _ in 0..output_count { + let input_ = input.clone(); + + match output::output_map(input_) { + Ok((i, o)) => { + println!("{:?}", o); + input = i; + } + Err(Err::Error(e)) => { + return Err(Err::Error(Error::append(i, ErrorKind::Count, e))) + } + Err(e) => return Err(e), + } + } + + Ok((i, Psbt { + version: global_map.version.unwrap_or(0), + transaction_version: global_map.transaction_version.unwrap_or(0), + fallback_lock_time: global_map.fallback_locktime, + tx_modifiable: global_map + .tx_modifiable + .unwrap_or_else(|| global::TxModifiable::empty()), + })) + } +} + +/// A Partially Signed Bitcoin Transaction (PSBT). +#[derive(Debug)] +pub struct Psbt { + /// Version of the PSBT file. + pub version: u32, + /// The version of the transaction. + pub transaction_version: u32, + /// The fallback lock time to use if no inputs specify a required lock + /// time. + pub fallback_lock_time: Option, + /// Flags indicating which parts of the transaction that are modifiable. + pub tx_modifiable: global::TxModifiable, +} + +#[cfg(test)] +mod tests { + use super::*; + use nom::error::VerboseError; + + #[test] + #[cfg(feature = "std")] + fn parse_psbt_invalid_test_vectors() { + let test_vectors = foundation_test_vectors::psbt::TestVectors::new(); + + for (i, test_vector) in test_vectors.invalid.iter().enumerate() { + println!("Test vector {i}: {}", test_vector.description); + psbt::>(&test_vector.data).unwrap_err(); + } + } + + #[test] + #[cfg(feature = "std")] + fn parse_psbt_valid_test_vectors() { + let test_vectors = foundation_test_vectors::psbt::TestVectors::new(); + + for (i, test_vector) in test_vectors.valid.iter().enumerate() { + println!("Test vector {i}: {}", test_vector.description); + match psbt::>(&test_vector.data) { + Err(e) => panic!("{e}"), + _ => (), + } + } + } +} diff --git a/psbt/src/parser/multi.rs b/psbt/src/parser/multi.rs new file mode 100644 index 0000000..3641f1c --- /dev/null +++ b/psbt/src/parser/multi.rs @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +//! Nom combinators applying their child parser multiple times. +//! +//! This provides `alloc` free alternatives to some [`nom`] combinators. + +use nom::{ + error::{ErrorKind, ParseError}, + Err, IResult, Parser, ToUsize, +}; + +/// Gets a number from the first parser, then applies the second parser that many +/// times calling `acc` to gather the results. +/// +/// This is a non-`alloc` version of [`nom::multi::length_count`]. +/// +/// # Arguments +/// +/// - `count`: The parser to apply to obtain the count from. +/// - `child_parser`: The parser to apply repeatedly. +/// - `init`: A function returning the initial value. +/// - `acc`: The accumulator function that will be repeatedly called with the +/// previous values and `child_parser` output. +/// +/// # Notes +/// +/// Consider contributing this to the [`nom`] crate. +pub fn length_count_fold( + mut count: Count, + mut child_parser: ChildParser, + mut init: Init, + mut acc: Accumulator, +) -> impl FnMut(Input) -> IResult +where + Input: Clone, + Count: Parser, + ChildParser: Parser, + N: ToUsize, + Init: FnMut() -> Result, + Accumulator: FnMut(Result, Output) -> Result, + Error: ParseError, +{ + move |i: Input| { + let (i, count) = count.parse(i)?; + let mut input = i.clone(); + let mut res = init(); + + for _ in 0..count.to_usize() { + let input_ = input.clone(); + + match child_parser.parse(input_) { + Ok((i, o)) => { + res = acc(res, o); + input = i; + } + Err(Err::Error(e)) => { + return Err(Err::Error(Error::append(i, ErrorKind::Count, e))) + } + Err(e) => return Err(e), + } + } + + Ok((input, res)) + } +} + +#[cfg(test)] +mod tests { + use crate::parser::multi::length_count_fold; + use nom::number::complete::u8; + use nom::IResult; + + #[test] + #[cfg(feature = "std")] + fn length_count_fold_zero() { + const INPUT: &[u8] = &[0]; + + let mut parser = length_count_fold(u8, u8, || 0, |_, _| unreachable!()); + + let res: IResult<_, _> = parser(INPUT); + let (i, n) = res.unwrap(); + + assert!(i.is_empty()); + assert_eq!(n, 0); + } + + #[test] + #[cfg(feature = "std")] + fn length_count_fold_bytes() { + const INPUT: &[u8] = &[5, 1, 2, 3, 4, 5]; + + let mut parser = length_count_fold( + u8, + u8, + || 0, + |mut index, n| { + index += 1; + assert_eq!(INPUT[index], n); + index + }, + ); + + let res: IResult<_, _> = parser(INPUT); + let (i, n) = res.unwrap(); + assert!(i.is_empty()); + assert_eq!(n, INPUT.len() - 1); + } +} diff --git a/psbt/src/parser/output.rs b/psbt/src/parser/output.rs new file mode 100644 index 0000000..cfc6b90 --- /dev/null +++ b/psbt/src/parser/output.rs @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +use nom::branch::alt; +use nom::combinator::{eof, map, rest}; +use nom::error::{FromExternalError, ParseError}; +use nom::number::complete::le_u64; +use nom::multi::fold_many0; +use nom::IResult; +use nom::bytes::complete::tag; +use nom::sequence::terminated; + +use secp256k1::{PublicKey, XOnlyPublicKey}; + +use foundation_bip32::{parser::key_source, KeySource}; + +use crate::parser::keypair::key_pair; +use crate::parser::secp::{public_key, x_only_public_key}; + +#[rustfmt::skip] +pub fn output_map<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], OutputMap<'a>, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, +{ + let keypairs = fold_many0( + output_key_pair, + OutputMap::default, + |mut map, key_pair| { + match key_pair { + KeyPair::RedeemScript(v) => map.redeem_script = Some(v), + KeyPair::WitnessScript(v) => map.witness_script = Some(v), + KeyPair::Bip32Derivation(_, _) => (), // TODO + KeyPair::Amount(v) => map.amount = Some(v), + KeyPair::Script(v) => map.script = Some(v), + KeyPair::TapInternalKey(v) => map.tap_internal_key = Some(v), + KeyPair::TapTree(v) => map.tap_tree = Some(v), + KeyPair::TapBip32Derivation(_, _) => (), // TODO + }; + + map + }, + ); + + terminated(keypairs, tag("\x00"))(i) +} + +#[rustfmt::skip] +fn output_key_pair<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], KeyPair<'a>, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, +{ + let redeem_script = key_pair(0x00, eof, rest); + let witness_script = key_pair(0x01, eof, rest); + let bip32_derivation = key_pair(0x02, public_key, key_source); + let amount = key_pair(0x03, eof, le_u64); + let script = key_pair(0x04, eof, rest); + let tap_internal_key = key_pair(0x05, eof, x_only_public_key); + let tap_tree = key_pair(0x06, eof, rest); + let tap_bip32_derivation = key_pair(0x06, x_only_public_key, rest); + + alt(( + map(redeem_script, |(_, v)| KeyPair::RedeemScript(v)), + map(witness_script, |(_, v)| KeyPair::WitnessScript(v)), + map(bip32_derivation, |(k, v)| KeyPair::Bip32Derivation(k, v)), + map(amount, |(_, v)| KeyPair::Amount(v)), + map(script, |(_, v)| KeyPair::Script(v)), + map(tap_internal_key, |(_, v)| KeyPair::TapInternalKey(v)), + map(tap_tree, |(_, v)| KeyPair::TapTree(v)), + map(tap_bip32_derivation, |(k, v)| KeyPair::TapBip32Derivation(k, v)), + ))(i) +} + +#[derive(Debug, Default)] +pub struct OutputMap<'a> { + pub redeem_script: Option<&'a [u8]>, + pub witness_script: Option<&'a [u8]>, + pub amount: Option, + pub script: Option<&'a [u8]>, + pub tap_internal_key: Option, + pub tap_tree: Option<&'a [u8]>, +} + +enum KeyPair<'a> { + RedeemScript(&'a [u8]), + WitnessScript(&'a [u8]), + Bip32Derivation(PublicKey, KeySource<'a>), + Amount(u64), + Script(&'a [u8]), + TapInternalKey(XOnlyPublicKey), + TapTree(&'a [u8]), + TapBip32Derivation(XOnlyPublicKey, &'a [u8]), +} diff --git a/psbt/src/parser/secp.rs b/psbt/src/parser/secp.rs new file mode 100644 index 0000000..0b36f5e --- /dev/null +++ b/psbt/src/parser/secp.rs @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +use nom::bytes::complete::take; +use nom::combinator::map_res; +use nom::error::ErrorKind; +use nom::error::FromExternalError; +use nom::error::ParseError; +use nom::Err; +use nom::IResult; + +use secp256k1::{ecdsa, schnorr, PublicKey, XOnlyPublicKey}; + +pub fn x_only_public_key<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], XOnlyPublicKey, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, +{ + map_res(take(32usize), |data| XOnlyPublicKey::from_slice(data))(i) +} + +pub fn public_key<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], PublicKey, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, +{ + match PublicKey::from_slice(i) { + Ok(p) => Ok((&i[i.len()..], p)), + Err(e) => Err(Err::Failure(Error::from_external_error( + i, + ErrorKind::Fail, + e, + ))), + } +} + +pub fn ecdsa_signature<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], ecdsa::Signature, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, +{ + match ecdsa::Signature::from_der(i) { + Ok(p) => Ok((&i[i.len()..], p)), + Err(e) => Err(Err::Failure(Error::from_external_error( + i, + ErrorKind::Fail, + e, + ))), + } +} + +pub fn schnorr_signature<'a, Error>(i: &'a [u8]) -> IResult<&'a [u8], schnorr::Signature, Error> +where + Error: ParseError<&'a [u8]>, + Error: FromExternalError<&'a [u8], secp256k1::Error>, +{ + match schnorr::Signature::from_slice(i) { + Ok(p) => Ok((&i[i.len()..], p)), + Err(e) => Err(Err::Failure(Error::from_external_error( + i, + ErrorKind::Fail, + e, + ))), + } +} diff --git a/psbt/src/parser/transaction.rs b/psbt/src/parser/transaction.rs new file mode 100644 index 0000000..5b09f1a --- /dev/null +++ b/psbt/src/parser/transaction.rs @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +use nom::{ + bytes::complete::take, combinator::map, error::ParseError, multi::length_data, + number::complete::le_i64, number::complete::le_u32, sequence::tuple, IResult, +}; + +use crate::parser::compact_size::compact_size; +use crate::parser::multi::length_count_fold; + +/// Parses a raw bitcoin transaction. +pub fn transaction<'a, Error>(i: &'a [u8]) -> IResult<&[u8], Transaction, Error> +where + Error: ParseError<&'a [u8]>, +{ + let version = le_u32; + let inputs = length_count_fold(compact_size, input, || 0, |acc, _| acc + 1); + let outputs = length_count_fold(compact_size, output, || 0, |acc, _| acc + 1); + let fields = tuple((version, inputs, outputs)); + + let mut parser = map(fields, |(version, input_count, output_count)| Transaction { + version, + input_count, + output_count, + }); + + parser(i) +} + +/// Parses a raw bitcoin transaction input. +pub fn input<'a, Error>(i: &'a [u8]) -> IResult<&[u8], Input, Error> +where + Error: ParseError<&'a [u8]>, +{ + let previous_output = output_point; + let script_sig = length_data(compact_size); + let sequence = le_u32; + let fields = tuple((previous_output, script_sig, sequence)); + + let mut parser = map(fields, |(previous_output, script_sig, sequence)| Input { + previous_output, + script_sig, + sequence, + }); + + parser(i) +} + +/// Parses a raw bitcoin transaction output. +pub fn output<'a, Error>(i: &'a [u8]) -> IResult<&[u8], Output, Error> +where + Error: ParseError<&'a [u8]>, +{ + let value = le_i64; + let script_pubkey = length_data(compact_size); + let fields = tuple((value, script_pubkey)); + let mut parser = map(fields, |(value, script_pubkey)| Output { + value, + script_pubkey, + }); + + parser(i) +} + +/// Parses a raw bitcoin transaction output point of a transaction input. +pub fn output_point<'a, Error: ParseError<&'a [u8]>>( + i: &'a [u8], +) -> IResult<&[u8], OutputPoint, Error> { + let hash = take(32usize); + let index = le_u32; + let mut parser = map(tuple((hash, index)), |(hash, index)| OutputPoint { + hash, + index, + }); + + parser(i) +} + +/// A raw bitcoin transaction. +#[derive(Debug, Clone)] +pub struct Transaction { + pub version: u32, + pub input_count: u32, + pub output_count: u32, +} + +/// A transaction input. +#[derive(Debug)] +pub struct Input<'a> { + pub previous_output: OutputPoint<'a>, + pub script_sig: &'a [u8], + pub sequence: u32, +} + +/// A transaction output. +#[derive(Debug)] +pub struct Output<'a> { + /// Number of satoshis this output is worth. + pub value: i64, + /// Script with the conditions to spend this output. + pub script_pubkey: &'a [u8], +} + +/// Points to the output of a transaction. +#[derive(Debug)] +pub struct OutputPoint<'a> { + /// The transaction ID of the transaction holding the output to spend. + pub hash: &'a [u8], + /// The output index number of the transaction to spend from the + /// transaction. + pub index: u32, +} diff --git a/psbt/src/taproot.rs b/psbt/src/taproot.rs new file mode 100644 index 0000000..daaee96 --- /dev/null +++ b/psbt/src/taproot.rs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +use bitcoin_hashes::sha256t_hash_newtype; +use secp256k1::XOnlyPublicKey; + +// Define our own Taproot TapLeaf hash type since the one in rust-bitcoin is +// unusable for us since that library doesn't work without `alloc`. +// +// TODO: +// - If this is available independently of rust-bitcoin, we should use that. +sha256t_hash_newtype! { + pub struct Leaf = hash_str("TapLeaf"); + + #[hash_newtype(forward)] + pub struct LeafHash(_); +} + +sha256t_hash_newtype! { + pub struct TapBranch = hash_str("TapBranch"); + + #[hash_newtype(forward)] + pub struct TapNodeHash(_); +} + +#[derive(Debug, Clone)] +pub struct TaprootScriptSignature { + pub x_only_public_key: XOnlyPublicKey, + pub leaf_hash: LeafHash, +} diff --git a/psbt/src/transaction.rs b/psbt/src/transaction.rs new file mode 100644 index 0000000..e69de29 diff --git a/psbt/src/verifier.rs b/psbt/src/verifier.rs new file mode 100644 index 0000000..b685a62 --- /dev/null +++ b/psbt/src/verifier.rs @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +//! This module will verify PSBT transactions for a given wallet.