{"id":19752370,"url":"https://github.com/subhra264/psbt_poc","last_synced_at":"2026-06-09T02:31:10.676Z","repository":{"id":179465065,"uuid":"652690231","full_name":"Subhra264/psbt_poc","owner":"Subhra264","description":"Contains different pocs for psbt integration in rust-bitcoin","archived":false,"fork":false,"pushed_at":"2023-07-07T13:17:43.000Z","size":14,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-28T08:44:19.566Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Subhra264.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-12T15:35:55.000Z","updated_at":"2023-08-28T04:08:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"ba11f5cb-f40b-4331-948d-398e3961f1cb","html_url":"https://github.com/Subhra264/psbt_poc","commit_stats":null,"previous_names":["subhra264/psbt_poc"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Subhra264/psbt_poc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Subhra264%2Fpsbt_poc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Subhra264%2Fpsbt_poc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Subhra264%2Fpsbt_poc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Subhra264%2Fpsbt_poc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Subhra264","download_url":"https://codeload.github.com/Subhra264/psbt_poc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Subhra264%2Fpsbt_poc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34089325,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T02:48:57.895Z","updated_at":"2026-06-09T02:31:10.580Z","avatar_url":"https://github.com/Subhra264.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Context\n- [Tracking PSBT refactoring \u0026 PSBTv2 Epic](https://github.com/rust-bitcoin/rust-bitcoin/issues/1115)\n- [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)\n- [BIP 370](https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki)\n\n## Approach 1: Implementation of Psbtv2 \u0026 Breaking Changes\n\n### PartiallySignedTransactionInner\n\nThe following approach replaces the `PartiallySignedTransaction` with `PartiallySignedTransactionInner` where all the version-specific fields are made `Option`. It comes with the flexibility to create both the Psbtv0 and Psbtv2 and convert between them. This involves breaking changes and requires existing implementations to be changed to support the new psbtv2 standard.\n\nIt assumes [this PR regarding the Psbt Version](https://github.com/rust-bitcoin/rust-bitcoin/pull/1218) to be merged and all the following changes are proposed on top of this PR.\n\n```rust\n// PartiallySignedTrasanction --\u003e PartiallySignedTransactionInner\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"serde\", derive(Serialize, Deserialize))]\n#[cfg_attr(feature = \"serde\", serde(crate = \"actual_serde\"))]\npub struct PartiallySignedTransactionInner {\n    /// The unsigned transaction, scriptSigs and witnesses for each input must be empty.\n    pub unsigned_tx: Option\u003cTransaction\u003e,\n    /// The version number of this PSBT. If omitted, the version number is V0.\n    /// See https://github.com/rust-bitcoin/rust-bitcoin/pull/1218\n    pub version: Version,\n\n    // ...\n\n    /// The corresponding key-value map for each input in the unsigned transaction.\n    pub inputs: Vec\u003cInput\u003e, // New Input, see below\n    /// The corresponding key-value map for each output in the unsigned transaction.\n    pub outputs: Vec\u003cOutput\u003e, // New Output, see below\n\n    // More new Psbtv2 Optional fields go here\n    /// 32-bit little endian signed integer representing the\n    /// version number of the transaction being created\n    pub tx_version: Option\u003ci32\u003e,\n    /// 32-bit little endian unsigned integer representing the transaction locktime\n    /// to use if no inputs specify a required locktime.\n    pub fallback_locktime: Option\u003cu32\u003e,\n    /// 8 bit unsigned integer as a bitfield for various transaction modification flags\n    pub tx_modifiable: Option\u003cu8\u003e, // or, Option\u003cTxModifiable\u003e\n}\n```\n\nIntroduction to a new `struct` named `Psbt` that internally stores and owns a `PartiallySignedTransactionInner` instance. A `Psbt` instance always guarantees that the underlying `inner` is validated.\n\n```rust\npub struct Psbt {\n    inner: PartiallySignedTransactionInner,\n}\n```\n\nA `PartiallySignedTransactionInner` first needs to be created with all the fields (at least the required ones) filled. The \"inner\" instance can not be directly used anywhere, instead, it must be validated first using the following method. The following factory method validates the psbt according to the inner's `version` enum field and finally returns a new `Psbt` instance.\n\n```rust\nimpl Psbt {\n    pub fn from_inner(psbt: PartiallySignedTransactionInner) -\u003e Result\u003cPsbt, String\u003e {\n        match validate_psbt_inner(\u0026psbt) {\n            Ok(()) =\u003e Ok(Psbt { inner: psbt }),\n            Err(err) =\u003e Err(err),\n        }\n    }\n\n    fn validate_psbt_inner(psbt: \u0026PartiallySignedTransactionInner) -\u003e Result\u003c(), String\u003e {\n        match psbt.version {\n            Version::PsbtV0 =\u003e {\n                // Some code to validate Psbt as a version 0 Psbt\n                // let valid = validate(psbt);\n                if !valid {\n                    Err(String::from(\"Error parsing psbtv0\"))\n                }\n            }\n            Version::Psbtv2 =\u003e {\n                // Some code to validate Psbt as a version 2 Psbt\n                // let valid = validate(psbt);\n                if !valid {\n                    Err(String::from(\"Error parsing psbtv2\"))\n                }\n            }\n        }\n        Ok(())\n    }\n}\n```\n\n### Inputs and Outputs\n\n```rust\npub struct Input {\n    // All existing Input fields\n    // ...\n\n    // Optional Psbtv2 fields\n    // use crate::hash_type::Txid;\n    pub previous_tx_id: Option\u003cTxid\u003e, // Required in PsbtV2.\n    pub output_index: Option\u003cu32\u003e, // Required in PsbtV2\n    // use crate::blockdata::transaction::Sequence\n    pub sequence: Option\u003cSequence\u003e, // Optional in PsbtV2, but not allowed in PsbtV0\n    pub required_time_locktime: Option\u003cu32\u003e, // Optional in PsbtV2, not allowed in PsbtV0\n    pub required_height_locktime: Option\u003cu32\u003e, // Optional in PsbtV2, not allowed in PsbtV0\n}\n\n/// A key-value map for an output of the corresponding index in the unsigned\n/// transaction.\n#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"serde\", derive(Serialize, Deserialize))]\n#[cfg_attr(feature = \"serde\", serde(crate = \"actual_serde\"))]\npub struct Output {\n    // All existing Output fields\n    // ...\n\n    // Psbtv2 compulsory fields\n    pub amount: Option\u003cAmount\u003e, // use crate::Amount;\n    // use crate::blockdata::script::ScriptBuf;\n    pub script: Option\u003cScriptBuf\u003e,\n}\n```\n\nInputs and Outputs are not directly verified. Instead while adding new inputs and outputs to the psbt we can validate them based on its version.\n\n```rust\nimpl Psbt {\n    pub fn add_input(\u0026self, input: Input) -\u003e Result\u003c(), String\u003e {\n        // Validate the input according to this psbt version\n        if input.validate(self.version) {\n            Ok(())\n        } else {\n            Err(\"Error validating input!\")\n        }\n    }\n\n    pub fn add_output(\u0026self, output: Output) -\u003e Result\u003c(), String\u003e {\n        // Validate the output according to this psbt version\n        if output.validate(self.version) {\n            Ok(())\n        } else {\n            Err(\"Error validating output!\")\n        }\n    }\n}\n```\n\n### Using Psbt\n\nInstead of giving direct access to `inner`, we can provide a getter -\n\n```rust\nimpl Psbt {\n    pub fn get_inner_ref(\u0026self) -\u003e \u0026PartiallySignedTransactionInner {\n        \u0026self.inner\n    }\n\n    /// Returns the internally stored `PartiallySignedTransactionInner`\n    ///\n    /// After making any changes, the developer needs to build another `Psbt`\n    /// instance using the updated PsbtInner.\n    pub fn to_inner(self) -\u003e PartiallySignedTransactionInner {\n        self.inner\n    }\n\n    // Or setters for Optional fields - PsbtV0 and PsbtV2 fields\n    // setters will do all the validations before making changes\n}\n```\n\n### Serialization \u0026 Deserialization\n\n```rust\nimpl PartiallySignedTransactionInner {\n    // Hide direct access to `PartiallySignedTransactionInner::serialize()`\n    pub(crate) fn serialize(\u0026self) -\u003e Vec\u003cu8\u003e {\n        // Code\n    }\n    \n    // Hide direct access to `PartiallySignedTransactionInner::deserialize()`\n    pub(crate) fn deserialize(bytes: \u0026[u8]) -\u003e Self {\n        // The PsbtV0 parser needs to be updated a little\n        // to recognize new PsbtV2 fields\n    }\n}\n\nimpl Psbt {\n    /// Wrapper around the `ParitallySignedTransactionInner::serialize` function.\n    ///\n    /// Since only the validated Psbts can be allowed to be serialized and\n    /// transmitted through the network, only the `Psbt::serialize()` function\n    /// is to be used by the developers for the serialization. \n    pub fn serialize(\u0026self) -\u003e Vec\u003cu8\u003e {\n        self.inner.serialize()\n    }\n\n    pub fn deserialize(bytes: \u0026[u8]) -\u003e Result\u003cSelf, Error\u003e {\n        // Internally uses the `PartiallySignedTransactionInner::deserialize()`\n        // function. The internal `deserialize` function decodes the bytes\n        // without validation. In the next step, `Psbt::from_inner` does all\n        // the validation and finally returns the decoded and validated Psbt\n        let psbt_inner = PartiallySignedTransactionInner::deserialize(\u0026bytes);\n        Psbt::from_inner(psbt_inner)\n    }\n}\n```\n\n### Conversion between PsbtV0 and PsbtV2\n\nIt is possible to create a psbtv0 out of a valid psbtv2 and vice versa (See https://gist.github.com/0xBEEFCAF3/8b7d7acee5ed0c7b84bb87cb53788394). We can create the unsigned transaction required in PsbtV0 using various fields available in PsbtV2.\n\n```rust\nimpl Psbt {\n    pub fn get_v2(self) -\u003e Self {\n        match self.version {\n            Version::PsbtV0 =\u003e {\n                // Convert Psbt to Version 2 Psbt\n                let v2_inputs = Vec::new\u003cInput\u003e();\n                let v2_outputs = Vec::new\u003cOutput\u003e();\n                let unsigned_tx = self.unsigned_tx?;\n\n                for input in self.inputs {\n                    // All the PsbtV0 input fields\n                    let v2_input = Input {\n                        // Fill new PsbtV2 fields using\n                        // `unsigned_tx.input`.\n                        ..input // Rest of the fields\n                    }\n\n                    v2_inputs.push(v2_input);\n                }\n\n                for output in self.outputs {\n                    let v2_output = Output {\n                        // Fill up `amount` and `script` from\n                        // `unsigned_tx.output`\n                        ..output // Rest of the output fields\n                    }\n\n                    v2_outputs.push(v2_output);\n                }\n\n                let psbt_inner = PartiallySignedTransactionInner {\n                    transaction: None,\n                    verison: Version::PsbtV2,\n                    inputs: v2_inputs,\n                    outputs: v2_outputs,\n                    // Other PsbtV2 fields\n                    // tx_version,\n                    // fallback_locktime,\n                    // tx_modifiable,\n                    ..self // Rest of the Psbt fields\n                };\n\n                Psbt::from_inner(psbt_inner).unwrap()\n            }\n            Version::PsbtV2 =\u003e self\n        }\n    }\n\n    pub fn get_v0(self) -\u003e Self {\n        match self.version {\n            Version::PsbtV0 =\u003e self,\n            Version::PsbtV2 =\u003e {\n                // Convert PsbtV2 to Version 0 Psbt\n                // Adding new inputs and outputs to PsbtV0 is not\n                // allowed, hence, tx_version, fallback_locktime and\n                // tx_modifiable flags will be discarded. All the\n                // `Transaction` fields are constructable from PsbtV2.\n                let v0_tx_inputs = Vec::new\u003cTxIn\u003e();\n                let v0_tx_outputs = Vec::new\u003cTxOut\u003e();\n\n                // Extract v0_inputs and v0_outputs from `self.inputs`\n                // and `self.outputs`.\n\n                let tx = Transaction {\n                    version: self.tx_version,\n                    lock_time: self.fallback_locktime,\n                    input: v0_tx_inputs,\n                    output: v0_tx_outputs,\n                };\n\n                let psbt_inner = PartiallySignedTransactionInner {\n                    unsigned_tx: tx,\n                    version: Version::PsbtV0,\n                    inputs: v0_inputs, // After dropping PsbtV2 fields\n                    outputs: v0_outputs, // After dropping PsbtV2 fields\n                    ..self // Rest of the Psbt fields\n                };\n\n                Psbt::from_inner(psbt_inner).unwrap()\n            }\n        }\n    }\n}\n```\n\n## Approach 2: Non-Breaking Changes\n\nThe previous approach introduces breaking changes but provides validation checks. Whereas the second approach avoids such breaking API changes, but it is now up to the developers to do all the validations.\n\n```rust\n// New optional PsbtV2 fields are added to the original PartiallySignedTransaction.\n// Hence no need to change existing implementations.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\n#[cfg_attr(feature = \"serde\", derive(Serialize, Deserialize))]\n#[cfg_attr(feature = \"serde\", serde(crate = \"actual_serde\"))]\npub struct PartiallySignedTransaction {\n    /// The unsigned transaction, scriptSigs and witnesses for each input must be empty.\n    pub unsigned_tx: Option\u003cTransaction\u003e,\n    /// The version number of this PSBT. If omitted, the version number is V0.\n    /// See https://github.com/rust-bitcoin/rust-bitcoin/pull/1218\n    pub version: Version,\n\n    // ...\n\n    /// The corresponding key-value map for each input in the unsigned transaction.\n    pub inputs: Vec\u003cInput\u003e, // New Input, see below\n    /// The corresponding key-value map for each output in the unsigned transaction.\n    pub outputs: Vec\u003cOutput\u003e, // New Output, see below\n\n    // More new Psbtv2 Optional fields go here\n    /// 32-bit little endian signed integer representing the\n    /// version number of the transaction being created\n    pub tx_version: Option\u003ci32\u003e,\n    /// 32-bit little endian unsigned integer representing the transaction locktime\n    /// to use if no inputs specify a required locktime.\n    pub fallback_locktime: Option\u003cu32\u003e,\n    /// 8 bit unsigned integer as a bitfield for various transaction modification flags\n    pub tx_modifiable: Option\u003cu8\u003e, // or, Option\u003cTxModifiable\u003e\n}\n```\n\n### Validation\n\n```rust\nimpl PartiallySignedTransaction {\n    /// Validates the Psbt according to the version\n    /// should be used by other functions for validation\n    pub fn validate(\u0026self) -\u003e Result\u003c(), String\u003e {\n        // Code\n    }\n\n    pub fn other_psbt_functions(\u0026self) -\u003e ReturnValue {\n        // First validate the psbt\n        match self.validate() {\n            Err(err) =\u003e {\n                // Do something\n            },\n            Ok(()) =\u003e {\n                // Code for further operations\n            }\n        }\n    }\n}\n```\nOpposite to the first approach, there is no guarantee that the created `PartiallySignedTransaction` is always validated. So all the Psbt functions need to first validate the Psbt internally before doing further operations, otherwise, developers using the library need to call the `validate` function for validation.\n\n### Using Psbt\n\nThe usage of `PartiallySignedTransaction` is the same as it is today.\n\n### Serializaion \u0026 Deserialization\n\n```rust\nimpl PartiallySignedTransaction {\n    pub fn serialize(\u0026self) -\u003e Vec\u003cu8\u003e {\n        // Before proceeding further, validate the Psbt first\n        match validate() {\n            Err(err) =\u003e panic!(err), // Or do proper error handling\n            Ok() =\u003e {\n                // Code to serialize\n            }\n        }\n    }\n\n    pub fn deserialize(bytes: \u0026[u8]) -\u003e PartiallySignedTransaction {\n        // Build the Psbt from the bytes\n    }\n}\n```\n\n### Inputs \u0026 Outputs\n\nImplementations of PsbtV2 `Input` and `Output` are the same as the first approach.\n\n### Conversion between PsbtV0 and PsbtV2\n\n```rust\nimpl PartiallySignedTransaction {\n    pub fn get_v2(\u0026self) -\u003e Result\u003cSelf, String\u003e {\n        // First it Psbt needs to be validated\n        validate().map_err(|err| handle_err(err));\n        match self.version {\n            PsbtV0 =\u003e {\n                // Similar to previous approach, but returns `PartiallySignedTransaction`\n            },\n            PsbtV2 =\u003e Ok(self)\n        }\n    }\n\n    pub fn get_v0(\u0026self) -\u003e Result\u003cSelf, String\u003e {\n        // First it needs to be validated\n        self.validate().map_err(|err| handle_err(err));\n        match self.version [\n            PsbtV0 =\u003e Ok(self),\n            PsbtV2 =\u003e {\n                // Similar to the previous approach, but returns `PartiallySignedTransaction`\n            }\n        ]\n    }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubhra264%2Fpsbt_poc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsubhra264%2Fpsbt_poc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubhra264%2Fpsbt_poc/lists"}