11use std:: collections:: HashSet ;
22
33use simplicityhl:: elements:: confidential:: Value as ConfidentialValue ;
4- use simplicityhl:: elements:: secp256k1_zkp:: ZERO_TWEAK ;
4+ use simplicityhl:: elements:: secp256k1_zkp:: { SECP256K1 , SecretKey , ZERO_TWEAK } ;
55use simplicityhl:: elements:: { AssetId , Script , Transaction } ;
66
77/// Constraints for verifying an issuance transaction.
@@ -20,11 +20,19 @@ pub struct IssuanceInputConstraints {
2020 /// Index into `tx.input`.
2121 pub input_idx : usize ,
2222
23- /// Destination and amount for the issued asset.
24- pub issuance_destination : Option < ( Script , u64 ) > ,
25-
26- /// Destination and amount for the reissuance token.
27- pub reissuance_destination : Option < ( Script , u64 ) > ,
23+ /// Destination, amount, and optional blinding key for the issued asset.
24+ ///
25+ /// The tuple is `(script, amount, blinding_key)`. When `blinding_key` is `Some`,
26+ /// confidential outputs are unblinded using it; if unblinding succeeds and the asset
27+ /// matches, the output is accounted for, otherwise it is skipped.
28+ pub issuance_destination : Option < ( Script , u64 , Option < SecretKey > ) > ,
29+
30+ /// Destination, amount, and optional blinding key for the reissuance token.
31+ ///
32+ /// The tuple is `(script, amount, blinding_key)`. When `blinding_key` is `Some`,
33+ /// confidential outputs are unblinded using it; if unblinding succeeds and the asset
34+ /// matches, the output is accounted for, otherwise it is skipped.
35+ pub reissuance_destination : Option < ( Script , u64 , Option < SecretKey > ) > ,
2836}
2937
3038#[ derive( thiserror:: Error , Debug , PartialEq , Eq ) ]
@@ -105,8 +113,12 @@ pub enum IssuanceVerificationError {
105113///
106114/// ## Confidentiality policy
107115///
108- /// - Outputs whose **asset is confidential** are ignored during verification (this verifier makes
109- /// no claims about what may be hidden in confidential-asset outputs).
116+ /// - Each destination tuple carries an optional blinding key (the third element). When set,
117+ /// confidential outputs are unblinded using the key. If unblinding succeeds and the asset
118+ /// matches, the output is accounted for. If unblinding fails, the output is silently skipped.
119+ /// - When no blinding key is provided for a destination, outputs whose **asset is confidential**
120+ /// are ignored during verification (this verifier makes no claims about what may be hidden in
121+ /// confidential-asset outputs).
110122/// - For constrained assets, any output with an **explicit matching asset** but a **non-explicit
111123/// value** fails verification (cannot check exact amounts).
112124///
@@ -241,13 +253,13 @@ fn verify_constrained_asset(
241253 tx : & Transaction ,
242254 asset_id : AssetId ,
243255 minted_amount : u64 ,
244- destination : Option < & ( Script , u64 ) > ,
256+ destination : Option < & ( Script , u64 , Option < SecretKey > ) > ,
245257 input_idx : usize ,
246258 kind : MintedConstraintKind ,
247259) -> Result < ( ) , IssuanceVerificationError > {
248- let ( dest_script, expected_amount) = match destination {
249- Some ( ( s, amt) ) => ( Some ( s) , * amt) ,
250- None => ( None , 0 ) ,
260+ let ( dest_script, expected_amount, blinder ) = match destination {
261+ Some ( ( s, amt, blinder ) ) => ( Some ( s) , * amt, Option :: from ( blinder ) ) ,
262+ None => ( None , 0 , None ) ,
251263 } ;
252264
253265 if minted_amount != expected_amount {
@@ -271,19 +283,20 @@ fn verify_constrained_asset(
271283 } ) ;
272284 }
273285
274- verify_asset_destination ( tx, asset_id, expected_amount, dest_script)
286+ verify_asset_destination ( tx, asset_id, expected_amount, dest_script, blinder )
275287}
276288
277289fn verify_asset_destination (
278290 tx : & Transaction ,
279291 asset_id : AssetId ,
280292 expected_amount : u64 ,
281293 destination_script : Option < & Script > ,
294+ blinding_key : Option < & SecretKey > ,
282295) -> Result < ( ) , IssuanceVerificationError > {
283296 let mut sum_to_destination = 0u64 ;
284297
285298 for ( vout, output) in tx. output . iter ( ) . enumerate ( ) {
286- match output. asset . explicit ( ) {
299+ let resolved = match output. asset . explicit ( ) {
287300 Some ( out_asset) if out_asset == asset_id => {
288301 let Some ( value) = output. value . explicit ( ) else {
289302 return Err (
@@ -293,24 +306,30 @@ fn verify_asset_destination(
293306 } ,
294307 ) ;
295308 } ;
309+ Some ( value)
310+ }
311+ _ => blinding_key
312+ . and_then ( |key| output. unblind ( SECP256K1 , * key) . ok ( ) )
313+ . filter ( |secrets| secrets. asset == asset_id)
314+ . map ( |secrets| secrets. value ) ,
315+ } ;
296316
297- let Some ( dest_script) = destination_script else {
298- return Err ( IssuanceVerificationError :: AssetAppearsInUnexpectedOutput {
299- vout,
300- asset_id,
301- } ) ;
302- } ;
303-
304- if output. script_pubkey != * dest_script {
305- return Err ( IssuanceVerificationError :: AssetAppearsInUnexpectedOutput {
306- vout,
307- asset_id,
308- } ) ;
309- }
317+ if let Some ( value) = resolved {
318+ let Some ( dest_script) = destination_script else {
319+ return Err ( IssuanceVerificationError :: AssetAppearsInUnexpectedOutput {
320+ vout,
321+ asset_id,
322+ } ) ;
323+ } ;
310324
311- sum_to_destination = sum_to_destination. saturating_add ( value) ;
325+ if output. script_pubkey != * dest_script {
326+ return Err ( IssuanceVerificationError :: AssetAppearsInUnexpectedOutput {
327+ vout,
328+ asset_id,
329+ } ) ;
312330 }
313- _ => { }
331+
332+ sum_to_destination = sum_to_destination. saturating_add ( value) ;
314333 }
315334 }
316335
@@ -435,10 +454,10 @@ mod tests {
435454 let constraints = IssuanceTxConstraints {
436455 inputs : vec ! [ IssuanceInputConstraints {
437456 input_idx: 0 ,
438- issuance_destination: Some ( ( issue_script, 50 ) ) ,
439- reissuance_destination: Some ( ( token_script, 1 ) ) ,
457+ issuance_destination: Some ( ( issue_script, 50 , None ) ) ,
458+ reissuance_destination: Some ( ( token_script, 1 , None ) ) ,
440459 } ] ,
441- allow_unconstrained_issuances : false ,
460+ .. Default :: default ( )
442461 } ;
443462
444463 assert_eq ! ( verify_issuance( & tx, & constraints) , Ok ( ( ) ) ) ;
@@ -464,10 +483,10 @@ mod tests {
464483 let constraints = IssuanceTxConstraints {
465484 inputs : vec ! [ IssuanceInputConstraints {
466485 input_idx: 0 ,
467- issuance_destination: Some ( ( issue_script, 10 ) ) ,
468- reissuance_destination: Some ( ( token_script, 1 ) ) ,
486+ issuance_destination: Some ( ( issue_script, 10 , None ) ) ,
487+ reissuance_destination: Some ( ( token_script, 1 , None ) ) ,
469488 } ] ,
470- allow_unconstrained_issuances : false ,
489+ .. Default :: default ( )
471490 } ;
472491
473492 assert ! ( matches!(
@@ -495,10 +514,10 @@ mod tests {
495514 let constraints = IssuanceTxConstraints {
496515 inputs : vec ! [ IssuanceInputConstraints {
497516 input_idx: 0 ,
498- issuance_destination: Some ( ( issue_script, 10 ) ) ,
499- reissuance_destination: Some ( ( token_script, 1 ) ) ,
517+ issuance_destination: Some ( ( issue_script, 10 , None ) ) ,
518+ reissuance_destination: Some ( ( token_script, 1 , None ) ) ,
500519 } ] ,
501- allow_unconstrained_issuances : false ,
520+ .. Default :: default ( )
502521 } ;
503522
504523 assert ! ( matches!(
@@ -527,9 +546,9 @@ mod tests {
527546 inputs : vec ! [ IssuanceInputConstraints {
528547 input_idx: 0 ,
529548 issuance_destination: None ,
530- reissuance_destination: Some ( ( token_script, 1 ) ) ,
549+ reissuance_destination: Some ( ( token_script, 1 , None ) ) ,
531550 } ] ,
532- allow_unconstrained_issuances : false ,
551+ .. Default :: default ( )
533552 } ;
534553
535554 assert ! ( matches!(
@@ -547,7 +566,7 @@ mod tests {
547566 issuance_destination: None ,
548567 reissuance_destination: None ,
549568 } ] ,
550- allow_unconstrained_issuances : false ,
569+ .. Default :: default ( )
551570 } ;
552571
553572 assert_eq ! (
@@ -577,10 +596,10 @@ mod tests {
577596 let constraints_strict = IssuanceTxConstraints {
578597 inputs : vec ! [ IssuanceInputConstraints {
579598 input_idx: 0 ,
580- issuance_destination: Some ( ( issue_script, 10 ) ) ,
581- reissuance_destination: Some ( ( token_script, 1 ) ) ,
599+ issuance_destination: Some ( ( issue_script, 10 , None ) ) ,
600+ reissuance_destination: Some ( ( token_script, 1 , None ) ) ,
582601 } ] ,
583- allow_unconstrained_issuances : false ,
602+ .. Default :: default ( )
584603 } ;
585604
586605 assert_eq ! (
@@ -621,15 +640,15 @@ mod tests {
621640 IssuanceInputConstraints {
622641 input_idx: 0 ,
623642 issuance_destination: None ,
624- reissuance_destination: Some ( ( taproot_gen. address. script_pubkey( ) , 1 ) ) ,
643+ reissuance_destination: Some ( ( taproot_gen. address. script_pubkey( ) , 1 , None ) ) ,
625644 } ,
626645 IssuanceInputConstraints {
627646 input_idx: 1 ,
628647 issuance_destination: None ,
629- reissuance_destination: Some ( ( taproot_gen. address. script_pubkey( ) , 1 ) ) ,
648+ reissuance_destination: Some ( ( taproot_gen. address. script_pubkey( ) , 1 , None ) ) ,
630649 } ,
631650 ] ,
632- allow_unconstrained_issuances : false ,
651+ .. Default :: default ( )
633652 } ;
634653
635654 verify_issuance ( & tx, & constraints) . map_err ( |e| format ! ( "Verification failed: {e:?}" ) ) ?;
0 commit comments