@@ -1690,3 +1690,248 @@ func TestUnknowns(t *testing.T) {
16901690 require .NoError (t , err )
16911691 require .Equal (t , packetWithUnknowns , encoded )
16921692}
1693+
1694+ // TestPsbtV2LifeCycle ensures that the full lifecycle of a PSBTv2 (creating,
1695+ // constructing, serializing, and extracting) works as expected.
1696+ func TestPsbtV2LifeCycle (t * testing.T ) {
1697+
1698+ // 1. Create a new V2 PSBT.
1699+ p , err := NewV2 (2 , 0 , 0 )
1700+ require .NoError (t , err )
1701+
1702+ // 2. Add an input with a specific sequence (e.g., 0 for RBF).
1703+ txid , _ := chainhash .NewHashFromStr ("0102030405060708091011121314151617181920212223242526272829303132" )
1704+
1705+ outPoint := wire .NewOutPoint (txid , 1 )
1706+ err = p .AddInput (* outPoint , 0 )
1707+ require .NoError (t , err )
1708+
1709+ // 3. Add an output.
1710+ script , _ := hex .DecodeString ("76a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac" )
1711+ err = p .AddOutput (100000000 , script )
1712+ require .NoError (t , err )
1713+
1714+ // 4. Serialize and Parse back.
1715+ var b bytes.Buffer
1716+ err = p .Serialize (& b )
1717+ require .NoError (t , err )
1718+
1719+ p2 , err := NewFromRawBytes (& b , false )
1720+ require .NoError (t , err )
1721+
1722+ // 5. Verify fields survived the round-trip.
1723+ require .Equal (t , uint32 (2 ), p2 .TxVersion )
1724+ require .Equal (t , uint32 (1 ), p2 .InputCount )
1725+ require .Equal (t , txid [:], p2 .Inputs [0 ].PreviousTxid )
1726+ require .Equal (t , uint32 (0 ), p2 .Inputs [0 ].Sequence )
1727+ require .Equal (t , int64 (100000000 ), p2 .Outputs [0 ].Amount )
1728+
1729+ // 6. Extract the transaction and verify it works.
1730+ // (Note: For extraction to work, we need to bypass the IsComplete check
1731+ // or finalize it. Since we are testing construction, we can test
1732+ // GetUnsignedTx directly).
1733+ msgTx , err := p2 .GetUnsignedTx ()
1734+ require .NoError (t , err )
1735+ require .Equal (t , int32 (2 ), msgTx .Version )
1736+ require .Equal (t , uint32 (0 ), msgTx .TxIn [0 ].Sequence )
1737+ require .Equal (t , int64 (100000000 ), msgTx .TxOut [0 ].Value )
1738+ }
1739+
1740+ // TestPsbtV2Validation verifies that PSBTv2 packets are validated correctly
1741+ // for strict versioning rules and mandatory field combinations.
1742+ func TestPsbtV2Validation (t * testing.T ) {
1743+ t .Run ("V2 cannot have global UnsignedTx" , func (t * testing.T ) {
1744+ // Construct raw bytes with both Version 2 AND UnsignedTx (0x00 forbidden in V2).
1745+ // Magic + Version (0xfb: 2) + UnsignedTx (0x00: minimal 1-byte tx)
1746+ raw := []byte {
1747+ 0x70 , 0x73 , 0x62 , 0x74 , 0xff , // Magic
1748+ 0x01 , 0xfb , 0x04 , 0x02 , 0x00 , 0x00 , 0x00 , // Version 2
1749+ 0x01 , 0x00 , 0x01 , 0x01 , // UnsignedTx (keyCode 0x00, minimal value)
1750+ 0x00 , // Separator
1751+ }
1752+
1753+ _ , err := NewFromRawBytes (bytes .NewReader (raw ), false )
1754+ require .Error (t , err )
1755+ })
1756+
1757+ t .Run ("V2 must have InputCount and OutputCount" , func (t * testing.T ) {
1758+ // Create a raw V2 serialization but manually omit counts.
1759+ // Magic + Version (0xfb: 2) + TxVersion (0x02: 2)
1760+ raw := []byte {
1761+ 0x70 , 0x73 , 0x62 , 0x74 , 0xff , // Magic
1762+ 0x01 , 0xfb , 0x04 , 0x02 , 0x00 , 0x00 , 0x00 , // Version 2
1763+ 0x01 , 0x02 , 0x04 , 0x02 , 0x00 , 0x00 , 0x00 , // TxVersion 2
1764+ 0x00 , // Separator
1765+ }
1766+
1767+ // parsing should fail because InputCount/OutputCount are missing for V2.
1768+ _ , err := NewFromRawBytes (bytes .NewReader (raw ), false )
1769+ require .Error (t , err )
1770+ })
1771+
1772+ t .Run ("Unsupported version should fail" , func (t * testing.T ) {
1773+ // Version 3 (unsupported)
1774+ raw := []byte {
1775+ 0x70 , 0x73 , 0x62 , 0x74 , 0xff , // Magic
1776+ 0x01 , 0xfb , 0x04 , 0x03 , 0x00 , 0x00 , 0x00 , // Version 3
1777+ 0x00 , // Separator
1778+ }
1779+
1780+ _ , err := NewFromRawBytes (bytes .NewReader (raw ), false )
1781+ require .Error (t , err )
1782+ })
1783+ }
1784+
1785+ // TestPsbtV2Counts ensures that the number of inputs and outputs parsed
1786+ // matches the InputCount and OutputCount global fields.
1787+ func TestPsbtV2Counts (t * testing.T ) {
1788+ // Create a V2 PSBT that claims 2 inputs but only provides 1.
1789+ p , err := NewV2 (2 , 0 , 0 )
1790+ require .NoError (t , err )
1791+
1792+ txid , _ := chainhash .NewHashFromStr ("0102030405060708091011121314151617181920212223242526272829303132" )
1793+ outPoint := wire .NewOutPoint (txid , 1 )
1794+ err = p .AddInput (* outPoint , 0xffffffff )
1795+ require .NoError (t , err )
1796+
1797+ script , _ := hex .DecodeString ("00" )
1798+ err = p .AddOutput (1000 , script )
1799+ require .NoError (t , err )
1800+
1801+ // Manually override counts to mismatch reality.
1802+ p .InputCount = 2
1803+ p .OutputCount = 1
1804+
1805+ var b bytes.Buffer
1806+ err = p .Serialize (& b )
1807+ require .NoError (t , err )
1808+
1809+ // Parsing should fail because we promised 2 inputs but only 1 followed.
1810+ _ , err = NewFromRawBytes (& b , false )
1811+ require .Error (t , err )
1812+ }
1813+
1814+ // TestPsbtV2Locktimes verifies that PSBTv2 locktime fields are correctly handled.
1815+ func TestPsbtV2Locktimes (t * testing.T ) {
1816+ // Create a V2 PSBT with a fallback locktime.
1817+ p , err := NewV2 (2 , 500000 , 0x01 ) // Fallback 500000, InputsModifiable
1818+ require .NoError (t , err )
1819+
1820+ txid , _ := chainhash .NewHashFromStr ("0102030405060708091011121314151617181920212223242526272829303132" )
1821+ outPoint := wire .NewOutPoint (txid , 1 )
1822+ err = p .AddInput (* outPoint , 0xffffffff )
1823+ require .NoError (t , err )
1824+
1825+ p .Inputs [0 ].HeightLocktime = 600000
1826+
1827+ msgTx , err := p .GetUnsignedTx ()
1828+ require .NoError (t , err )
1829+
1830+ // Since we have a height locktime in an input, it should take precedence.
1831+ require .Equal (t , uint32 (600000 ), msgTx .LockTime )
1832+
1833+ // Now try with time locktime.
1834+ p .Inputs [0 ].HeightLocktime = 0
1835+ p .Inputs [0 ].TimeLocktime = 1600000000
1836+ msgTx , err = p .GetUnsignedTx ()
1837+ require .NoError (t , err )
1838+ require .Equal (t , uint32 (1600000000 ), msgTx .LockTime )
1839+
1840+ // Test combined locktimes (BIP suggests highest value for same type,
1841+ // but here we just check our extraction logic).
1842+ err = p .AddInput (* outPoint , 0xffffffff )
1843+ require .NoError (t , err )
1844+ p .Inputs [1 ].TimeLocktime = 1700000000
1845+
1846+ msgTx , err = p .GetUnsignedTx ()
1847+ require .NoError (t , err )
1848+ require .Equal (t , uint32 (1700000000 ), msgTx .LockTime )
1849+
1850+ // Test modifiability flag.
1851+ require .Equal (t , uint8 (0x01 ), p .TxModifiable )
1852+ }
1853+
1854+ // TestPSBTv2DetermineLockTimeAlgorithm tests the comprehensive BIP-370 lock time determination algorithm
1855+ func TestPSBTv2DetermineLockTimeAlgorithm (t * testing.T ) {
1856+ t .Run ("Height-based preference when both supported (BIP-370 tie-breaker)" , func (t * testing.T ) {
1857+ p , err := NewV2 (2 , 100000 , 0 )
1858+ require .NoError (t , err )
1859+
1860+ txid , _ := chainhash .NewHashFromStr ("1111111111111111111111111111111111111111111111111111111111111111" )
1861+
1862+ // Input 1: Both time and height (flexible)
1863+ p .AddInput (* wire .NewOutPoint (txid , 0 ), 0 )
1864+ p .Inputs [0 ].TimeLocktime = 1600000000
1865+ p .Inputs [0 ].HeightLocktime = 550000
1866+
1867+ // Input 2: Both time and height (flexible)
1868+ p .AddInput (* wire .NewOutPoint (txid , 1 ), 0 )
1869+ p .Inputs [1 ].TimeLocktime = 1650000000
1870+ p .Inputs [1 ].HeightLocktime = 600000
1871+
1872+ // BIP-370: "height-based must be chosen" when both supported
1873+ lockTime , err := p .DetermineLockTime ()
1874+ require .NoError (t , err )
1875+ require .Equal (t , uint32 (600000 ), lockTime ) // Max height, NOT max time
1876+ })
1877+
1878+ t .Run ("Conflicting requirements should error" , func (t * testing.T ) {
1879+ p , err := NewV2 (2 , 0 , 0 )
1880+ require .NoError (t , err )
1881+
1882+ txid , _ := chainhash .NewHashFromStr ("2222222222222222222222222222222222222222222222222222222222222222" )
1883+
1884+ // Input 1: Time-only (cannot satisfy height)
1885+ p .AddInput (* wire .NewOutPoint (txid , 0 ), 0 )
1886+ p .Inputs [0 ].TimeLocktime = 1600000000
1887+
1888+ // Input 2: Height-only (cannot satisfy time)
1889+ p .AddInput (* wire .NewOutPoint (txid , 1 ), 0 )
1890+ p .Inputs [1 ].HeightLocktime = 500000
1891+
1892+ // Should fail - conflicting requirements
1893+ _ , err = p .DetermineLockTime ()
1894+ require .Error (t , err )
1895+ require .Equal (t , ErrInvalidPsbtFormat , err )
1896+ })
1897+
1898+ t .Run ("Fallback locktime when no constraints" , func (t * testing.T ) {
1899+ fallback := uint32 (123456 )
1900+ p , err := NewV2 (2 , fallback , 0 )
1901+ require .NoError (t , err )
1902+
1903+ txid , _ := chainhash .NewHashFromStr ("3333333333333333333333333333333333333333333333333333333333333333" )
1904+ p .AddInput (* wire .NewOutPoint (txid , 0 ), 0 )
1905+ // No TimeLocktime or HeightLocktime set
1906+
1907+ lockTime , err := p .DetermineLockTime ()
1908+ require .NoError (t , err )
1909+ require .Equal (t , fallback , lockTime )
1910+ })
1911+ }
1912+
1913+ // TestPSBTv2AddUnknownFields tests the addUnknown field handling
1914+ func TestPSBTv2AddUnknownFields (t * testing.T ) {
1915+ p , err := NewV2 (2 , 0 , 0 )
1916+ require .NoError (t , err )
1917+
1918+ txid , _ := chainhash .NewHashFromStr ("4444444444444444444444444444444444444444444444444444444444444444" )
1919+ p .AddInput (* wire .NewOutPoint (txid , 0 ), 0 )
1920+
1921+ // Test adding unknown field succeeds
1922+ err = p .Inputs [0 ].addUnknown (0xfc , []byte {0x01 , 0x02 }, []byte {0x03 , 0x04 })
1923+ require .NoError (t , err )
1924+ require .Len (t , p .Inputs [0 ].Unknowns , 1 )
1925+
1926+ // Test duplicate detection
1927+ err = p .Inputs [0 ].addUnknown (0xfc , []byte {0x01 , 0x02 }, []byte {0x03 , 0x04 })
1928+ require .Error (t , err )
1929+ require .Equal (t , ErrDuplicateKey , err )
1930+
1931+ p .AddOutput (1000000 , []byte {0x76 , 0xa9 , 0x14 })
1932+
1933+ // Test output unknown fields
1934+ err = p .Outputs [0 ].addUnknown (0xfd , []byte {0x05 }, []byte {0x06 })
1935+ require .NoError (t , err )
1936+ require .Len (t , p .Outputs [0 ].Unknowns , 1 )
1937+ }
0 commit comments