@@ -1629,4 +1629,112 @@ mod tests {
16291629 }
16301630 tx. verify_tx_amt_proofs ( & secp, & utxos) . unwrap ( ) ;
16311631 }
1632+
1633+ #[ test]
1634+ fn verify_explicit_output_fields ( ) {
1635+ let asset = AssetId :: LIQUID_BTC ;
1636+ let explicit = |value, script_pubkey| TxOut { script_pubkey, ..TxOut :: new_fee ( value, asset) } ;
1637+ let op_return = Script :: new_op_return ( b"data" ) ;
1638+ let spk = Script :: from ( vec ! [ 0x51 ] ) ;
1639+ let tx = Transaction {
1640+ version : 2 ,
1641+ lock_time : crate :: LockTime :: ZERO ,
1642+ input : vec ! [ TxIn :: default ( ) ] ,
1643+ output : vec ! [
1644+ explicit( 0 , op_return. clone( ) ) ,
1645+ explicit( 90 , spk. clone( ) ) ,
1646+ TxOut :: new_fee( 10 , asset) ,
1647+ ] ,
1648+ } ;
1649+ let spent_utxo = [ TxOut :: new_fee ( 100 , asset) ] ;
1650+ let verify = |tx : & Transaction | tx. verify_tx_amt_proofs ( SECP256K1 , & spent_utxo) ;
1651+ let verify_with = |index : usize , out : TxOut | {
1652+ let mut tx = tx. clone ( ) ;
1653+ tx. output [ index] = out;
1654+ verify ( & tx)
1655+ } ;
1656+
1657+ assert_eq ! ( verify( & tx) , Ok ( ( ) ) ) ;
1658+ let oversized = Script :: from ( vec ! [ 0x51 ; 10_001 ] ) ;
1659+ assert_eq ! ( verify_with( 0 , explicit( 0 , oversized) ) , Ok ( ( ) ) ) ;
1660+ assert_eq ! ( verify_with( 0 , TxOut :: new_fee( 0 , asset) ) , Ok ( ( ) ) ) ;
1661+ assert_eq ! (
1662+ verify_with( 2 , TxOut :: new_fee( 11 , asset) ) ,
1663+ Err ( VerificationError :: BalanceCheckFailed )
1664+ ) ;
1665+
1666+ assert_eq ! (
1667+ verify_with( 1 , explicit( 0 , spk. clone( ) ) ) ,
1668+ Err ( VerificationError :: TxOutError ( 1 , TxOutError :: NonUnspendableZeroValue ) )
1669+ ) ;
1670+ assert_eq ! (
1671+ verify_with( 0 , TxOut { asset: Asset :: Null , ..explicit( 0 , op_return. clone( ) ) } ) ,
1672+ Err ( VerificationError :: TxOutError ( 0 , TxOutError :: UnExpectedNullAsset ) )
1673+ ) ;
1674+ let asset_bf = AssetBlindingFactor :: new ( & mut thread_rng ( ) ) ;
1675+ let blinded_asset = Asset :: new_confidential ( SECP256K1 , asset, asset_bf) ;
1676+ assert_eq ! (
1677+ verify_with( 0 , TxOut { asset: blinded_asset, ..explicit( 0 , op_return. clone( ) ) } ) ,
1678+ Err ( VerificationError :: SurjectionProofMissing ( 0 ) )
1679+ ) ;
1680+
1681+ let vbf = ValueBlindingFactor :: new ( & mut thread_rng ( ) ) ;
1682+ let blinded_value =
1683+ Value :: new_confidential_from_assetid ( SECP256K1 , 90 , asset, vbf, AssetBlindingFactor :: zero ( ) ) ;
1684+ let blinded_value_out = TxOut { value : blinded_value, ..explicit ( 90 , spk. clone ( ) ) } ;
1685+ assert_eq ! (
1686+ verify_with( 1 , blinded_value_out. clone( ) ) ,
1687+ Err ( VerificationError :: RangeProofMissing ( 1 ) )
1688+ ) ;
1689+
1690+ let gen = Generator :: new_unblinded ( SECP256K1 , asset. into_tag ( ) ) ;
1691+ let comm = blinded_value. commitment ( ) . unwrap ( ) ;
1692+ // Unlike `blind_value_proof`, this commits to `spk`, so it is valid for `blinded_value_out`.
1693+ let rangeproof = RangeProof :: new (
1694+ SECP256K1 ,
1695+ 90 ,
1696+ comm,
1697+ 90 ,
1698+ vbf. into_inner ( ) ,
1699+ & [ ] ,
1700+ spk. as_bytes ( ) ,
1701+ SecretKey :: new ( & mut thread_rng ( ) ) ,
1702+ -1 ,
1703+ 0 ,
1704+ gen,
1705+ )
1706+ . unwrap ( ) ;
1707+ let surjection_proof =
1708+ SurjectionProof :: blind_asset_proof ( & mut thread_rng ( ) , SECP256K1 , asset, asset_bf) . unwrap ( ) ;
1709+ let witness = |surjection_proof, rangeproof| TxOutWitness { surjection_proof, rangeproof } ;
1710+ // The only input's asset is the domain, so `surjection_proof` is valid for `blinded_asset`.
1711+ let zero_blinded_asset = |rangeproof| TxOut {
1712+ asset : blinded_asset,
1713+ witness : witness ( surjection_proof. clone ( ) , rangeproof) ,
1714+ ..explicit ( 0 , op_return. clone ( ) )
1715+ } ;
1716+ assert_eq ! ( verify_with( 0 , zero_blinded_asset( RangeProof :: EMPTY ) ) , Ok ( ( ) ) ) ;
1717+ // Valid proofs, but for nothing an explicit value or asset commits to.
1718+ assert_eq ! (
1719+ verify_with( 0 , zero_blinded_asset( rangeproof. clone( ) ) ) ,
1720+ Err ( VerificationError :: RangeProofError ( 0 , secp256k1_zkp:: Error :: InvalidRangeProof ) )
1721+ ) ;
1722+ let explicit_rangeproof = TxOut {
1723+ witness : witness ( SurjectionProof :: EMPTY , rangeproof. clone ( ) ) ,
1724+ ..explicit ( 90 , spk)
1725+ } ;
1726+ assert_eq ! (
1727+ verify_with( 1 , explicit_rangeproof) ,
1728+ Err ( VerificationError :: RangeProofError ( 1 , secp256k1_zkp:: Error :: InvalidRangeProof ) )
1729+ ) ;
1730+ let explicit_asset_surjection_proof =
1731+ TxOut { witness : witness ( surjection_proof, rangeproof) , ..blinded_value_out } ;
1732+ assert_eq ! (
1733+ verify_with( 1 , explicit_asset_surjection_proof) ,
1734+ Err ( VerificationError :: SurjectionProofError (
1735+ 1 ,
1736+ secp256k1_zkp:: Error :: InvalidSurjectionProof
1737+ ) )
1738+ ) ;
1739+ }
16321740}
0 commit comments