-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SFT-UNKN: Add foundation-psbt crate.
Signed-off-by: Jean-Pierre De Jesus DIAZ <[email protected]>
- Loading branch information
Showing
18 changed files
with
1,211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" } | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.