55
66use super :: envelope:: build_inscription_script;
77use crate :: error:: WasmUtxoError ;
8+ use miniscript:: bitcoin:: consensus:: Encodable ;
9+ use miniscript:: bitcoin:: hashes:: hex:: FromHex ;
810use miniscript:: bitcoin:: hashes:: Hash ;
911use miniscript:: bitcoin:: key:: UntweakedKeypair ;
10- use miniscript:: bitcoin:: psbt:: Psbt ;
1112use miniscript:: bitcoin:: secp256k1:: { Secp256k1 , SecretKey , XOnlyPublicKey } ;
1213use miniscript:: bitcoin:: sighash:: { Prevouts , SighashCache } ;
1314use miniscript:: bitcoin:: taproot:: { ControlBlock , LeafVersion , TapLeafHash , TaprootBuilder } ;
1415use miniscript:: bitcoin:: { ScriptBuf , Transaction , TxOut , Witness } ;
1516
17+ /// NUMS point (Nothing Up My Sleeve) - a secp256k1 x coordinate with unknown discrete logarithm.
18+ /// Equal to SHA256(uncompressedDER(SECP256K1_GENERATOR_POINT)).
19+ /// Used as internal key when key-path spending is disabled.
20+ /// This matches utxo-lib's implementation for compatibility.
21+ const NUMS_POINT_HEX : & str = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" ;
22+
23+ fn nums_point ( ) -> XOnlyPublicKey {
24+ let bytes = Vec :: < u8 > :: from_hex ( NUMS_POINT_HEX ) . expect ( "valid hex" ) ;
25+ XOnlyPublicKey :: from_slice ( & bytes) . expect ( "valid x-only pubkey" )
26+ }
27+
1628/// Taproot leaf script data needed for spending
1729#[ derive( Debug , Clone ) ]
1830pub struct TapLeafScript {
@@ -33,32 +45,32 @@ pub struct InscriptionRevealData {
3345/// Create inscription reveal data including the commit output script and tap leaf script
3446///
3547/// # Arguments
36- /// * `internal_key ` - The x-only public key (32 bytes)
48+ /// * `script_pubkey ` - The x-only public key for the OP_CHECKSIG in the inscription script
3749/// * `content_type` - MIME type of the inscription
3850/// * `data` - The inscription data
3951///
4052/// # Returns
4153/// `InscriptionRevealData` containing the commit output script, estimated vsize, and tap leaf script
4254pub fn create_inscription_reveal_data (
43- internal_key : & XOnlyPublicKey ,
55+ script_pubkey : & XOnlyPublicKey ,
4456 content_type : & str ,
4557 data : & [ u8 ] ,
4658) -> Result < InscriptionRevealData , WasmUtxoError > {
4759 let secp = Secp256k1 :: new ( ) ;
4860
49- // Build the inscription script
50- let script = build_inscription_script ( internal_key , content_type, data) ;
61+ // Build the inscription script (pubkey is used for OP_CHECKSIG inside the script)
62+ let script = build_inscription_script ( script_pubkey , content_type, data) ;
5163
5264 // Create taproot tree with the inscription script as the only leaf
5365 let builder = TaprootBuilder :: new ( )
5466 . add_leaf ( 0 , script. clone ( ) )
5567 . map_err ( |e| WasmUtxoError :: new ( & format ! ( "Failed to build taproot tree: {:?}" , e) ) ) ?;
5668
57- // Finalize the taproot spend info
58- // Use an unspendable internal key (all zeros XOR'd with script root)
59- // For simplicity, we use the provided internal_key
69+ // Use NUMS point as internal key (disables key-path spending)
70+ // This matches utxo-lib's behavior for compatibility
71+ let internal_key = nums_point ( ) ;
6072 let spend_info = builder
61- . finalize ( & secp, * internal_key)
73+ . finalize ( & secp, internal_key)
6274 . map_err ( |e| WasmUtxoError :: new ( & format ! ( "Failed to finalize taproot: {:?}" , e) ) ) ?;
6375
6476 // Get the output script (network-agnostic)
@@ -94,15 +106,15 @@ pub fn create_inscription_reveal_data(
94106/// * `output_value_sats` - Value in satoshis for the inscription output
95107///
96108/// # Returns
97- /// A signed PSBT containing the reveal transaction
109+ /// The signed reveal transaction as bytes (ready to broadcast)
98110pub fn sign_reveal_transaction (
99111 private_key : & SecretKey ,
100112 tap_leaf_script : & TapLeafScript ,
101113 commit_tx : & Transaction ,
102114 commit_output_script : & [ u8 ] ,
103115 recipient_output_script : & [ u8 ] ,
104116 output_value_sats : u64 ,
105- ) -> Result < Psbt , WasmUtxoError > {
117+ ) -> Result < Vec < u8 > , WasmUtxoError > {
106118 let secp = Secp256k1 :: new ( ) ;
107119
108120 // Convert output scripts
@@ -187,14 +199,13 @@ pub fn sign_reveal_transaction(
187199 witness. push ( control_block. serialize ( ) ) ;
188200 reveal_tx. input [ 0 ] . witness = witness;
189201
190- // Create PSBT from finalized transaction
191- let psbt = Psbt :: from_unsigned_tx ( reveal_tx. clone ( ) )
192- . map_err ( |e| WasmUtxoError :: new ( & format ! ( "Failed to create PSBT: {}" , e) ) ) ?;
202+ // Serialize the signed transaction
203+ let mut tx_bytes = Vec :: new ( ) ;
204+ reveal_tx
205+ . consensus_encode ( & mut tx_bytes)
206+ . map_err ( |e| WasmUtxoError :: new ( & format ! ( "Failed to serialize transaction: {}" , e) ) ) ?;
193207
194- // Note: The PSBT is created from the signed transaction for compatibility
195- // with the expected return type. In practice, this is already finalized.
196-
197- Ok ( psbt)
208+ Ok ( tx_bytes)
198209}
199210
200211/// Estimate the virtual size of a reveal transaction
@@ -224,6 +235,7 @@ fn estimate_reveal_vsize(script: &ScriptBuf, control_block: &ControlBlock) -> us
224235#[ cfg( test) ]
225236mod tests {
226237 use super :: * ;
238+ use miniscript:: bitcoin:: hashes:: hex:: FromHex ;
227239
228240 fn test_keypair ( ) -> ( SecretKey , XOnlyPublicKey ) {
229241 let secp = Secp256k1 :: new ( ) ;
@@ -247,4 +259,123 @@ mod tests {
247259 assert ! ( !data. tap_leaf_script. script. is_empty( ) ) ;
248260 assert ! ( !data. tap_leaf_script. control_block. is_empty( ) ) ;
249261 }
262+
263+ /// Test with the same x-only pubkey as utxo-ord test
264+ /// Expected output script: 5120dc8b12eec336e7215fd1213acf66fb0d5dd962813c0616988a12c08493831109
265+ /// Expected address: tb1pmj939mkrxmnjzh73yyav7ehmp4wajc5p8srpdxy2ztqgfyurzyys4sg9zx
266+ #[ test]
267+ fn test_utxo_ord_fixture_short_data ( ) {
268+ // Same x-only pubkey as utxo-ord test
269+ let xonly_hex = "af455f4989d122e9185f8c351dbaecd13adca3eef8a9d38ef8ffed6867e342e3" ;
270+ let xonly_bytes = Vec :: < u8 > :: from_hex ( xonly_hex) . unwrap ( ) ;
271+ let pubkey = XOnlyPublicKey :: from_slice ( & xonly_bytes) . unwrap ( ) ;
272+
273+ let inscription_data = b"Never Gonna Give You Up" ;
274+ let result = create_inscription_reveal_data ( & pubkey, "text/plain" , inscription_data) ;
275+
276+ assert ! ( result. is_ok( ) ) ;
277+ let data = result. unwrap ( ) ;
278+
279+ // Log the actual output for debugging
280+ let output_hex = hex:: encode ( & data. output_script ) ;
281+ println ! ( "X-only pubkey: {}" , xonly_hex) ;
282+ println ! (
283+ "Inscription data: {:?}" ,
284+ String :: from_utf8_lossy( inscription_data)
285+ ) ;
286+ println ! ( "Output script (actual): {}" , output_hex) ;
287+ println ! ( "Output script (expected): 5120dc8b12eec336e7215fd1213acf66fb0d5dd962813c0616988a12c08493831109" ) ;
288+
289+ // Log the tap leaf script for debugging
290+ println ! (
291+ "Tap leaf script hex: {}" ,
292+ hex:: encode( & data. tap_leaf_script. script)
293+ ) ;
294+ println ! (
295+ "Control block hex: {}" ,
296+ hex:: encode( & data. tap_leaf_script. control_block)
297+ ) ;
298+
299+ // Basic structure checks
300+ assert_eq ! ( data. output_script. len( ) , 34 ) ;
301+ assert_eq ! ( data. output_script[ 0 ] , 0x51 ) ; // OP_1
302+ assert_eq ! ( data. output_script[ 1 ] , 0x20 ) ; // PUSH32
303+
304+ // Assert byte-exact match with utxo-ord
305+ let expected_hex = "5120dc8b12eec336e7215fd1213acf66fb0d5dd962813c0616988a12c08493831109" ;
306+ assert_eq ! (
307+ output_hex, expected_hex,
308+ "Output script should match utxo-ord fixture"
309+ ) ;
310+ }
311+
312+ /// Test with large data (>520 bytes) - same as utxo-ord test
313+ /// Expected output script: 5120ec90ba87f3e7c5462eb2173afdc50e00cea6fc69166677171d70f45dfb3a31b8
314+ /// Expected address: tb1pajgt4plnulz5vt4jzua0m3gwqr82dlrfzen8w9cawr69m7e6xxuq7dzypl
315+ #[ test]
316+ fn test_utxo_ord_fixture_large_data ( ) {
317+ let xonly_hex = "af455f4989d122e9185f8c351dbaecd13adca3eef8a9d38ef8ffed6867e342e3" ;
318+ let xonly_bytes = Vec :: < u8 > :: from_hex ( xonly_hex) . unwrap ( ) ;
319+ let pubkey = XOnlyPublicKey :: from_slice ( & xonly_bytes) . unwrap ( ) ;
320+
321+ // "Never Gonna Let You Down" repeated 100 times
322+ let base = b"Never Gonna Let You Down" ;
323+ let inscription_data: Vec < u8 > = base
324+ . iter ( )
325+ . cycle ( )
326+ . take ( base. len ( ) * 100 )
327+ . copied ( )
328+ . collect ( ) ;
329+
330+ let result = create_inscription_reveal_data ( & pubkey, "text/plain" , & inscription_data) ;
331+
332+ assert ! ( result. is_ok( ) ) ;
333+ let data = result. unwrap ( ) ;
334+
335+ let output_hex = hex:: encode ( & data. output_script ) ;
336+ println ! ( "Output script (actual): {}" , output_hex) ;
337+ println ! ( "Output script (expected): 5120ec90ba87f3e7c5462eb2173afdc50e00cea6fc69166677171d70f45dfb3a31b8" ) ;
338+
339+ assert_eq ! ( data. output_script. len( ) , 34 ) ;
340+ assert_eq ! ( data. output_script[ 0 ] , 0x51 ) ;
341+ assert_eq ! ( data. output_script[ 1 ] , 0x20 ) ;
342+
343+ // Assert byte-exact match with utxo-ord
344+ let expected_hex = "5120ec90ba87f3e7c5462eb2173afdc50e00cea6fc69166677171d70f45dfb3a31b8" ;
345+ assert_eq ! (
346+ output_hex, expected_hex,
347+ "Output script should match utxo-ord fixture"
348+ ) ;
349+ }
350+
351+ /// Debug test to understand taproot key tweaking
352+ #[ test]
353+ fn test_taproot_tweak_details ( ) {
354+ let xonly_hex = "af455f4989d122e9185f8c351dbaecd13adca3eef8a9d38ef8ffed6867e342e3" ;
355+ let xonly_bytes = Vec :: < u8 > :: from_hex ( xonly_hex) . unwrap ( ) ;
356+ let internal_key = XOnlyPublicKey :: from_slice ( & xonly_bytes) . unwrap ( ) ;
357+
358+ println ! ( "Internal key: {}" , internal_key) ;
359+
360+ let secp = Secp256k1 :: new ( ) ;
361+ let script =
362+ build_inscription_script ( & internal_key, "text/plain" , b"Never Gonna Give You Up" ) ;
363+
364+ println ! ( "Inscription script hex: {}" , hex:: encode( script. as_bytes( ) ) ) ;
365+ println ! ( "Inscription script len: {}" , script. len( ) ) ;
366+
367+ // Build taproot tree
368+ let builder = TaprootBuilder :: new ( ) . add_leaf ( 0 , script. clone ( ) ) . unwrap ( ) ;
369+
370+ let spend_info = builder. finalize ( & secp, internal_key) . unwrap ( ) ;
371+
372+ println ! ( "Output key (tweaked): {}" , spend_info. output_key( ) ) ;
373+ println ! (
374+ "Merkle root: {:?}" ,
375+ spend_info. merkle_root( ) . map( |r| r. to_string( ) )
376+ ) ;
377+
378+ let output_script = ScriptBuf :: new_p2tr_tweaked ( spend_info. output_key ( ) ) ;
379+ println ! ( "Output script: {}" , hex:: encode( output_script. as_bytes( ) ) ) ;
380+ }
250381}
0 commit comments