@@ -400,3 +400,88 @@ pub fn get_generator_size() -> u32 {
400400pub fn get_zero_tweak ( ) -> Buffer {
401401 Buffer :: from ( ZERO_TWEAK . as_ref ( ) . to_vec ( ) )
402402}
403+
404+ #[ napi( object) ]
405+ pub struct CreatedShieldedOutput {
406+ pub ephemeral_pubkey : Buffer ,
407+ pub commitment : Buffer ,
408+ pub range_proof : Buffer ,
409+ pub blinding_factor : Buffer ,
410+ pub asset_commitment : Option < Buffer > ,
411+ pub asset_blinding_factor : Option < Buffer > ,
412+ }
413+
414+ /// Create a FullShielded output with both value blinding factor and asset blinding factor
415+ /// provided externally. This is needed for the last output in a FullShielded transaction
416+ /// where the balance equation requires pre-computing the vbf using a known abf.
417+ #[ napi]
418+ pub fn create_shielded_output_with_both_blindings (
419+ value : i64 ,
420+ recipient_pubkey : Buffer ,
421+ token_uid : Buffer ,
422+ value_blinding_factor : Buffer ,
423+ asset_blinding_factor : Buffer ,
424+ ) -> napi:: Result < CreatedShieldedOutput > {
425+ use secp256k1_zkp:: SECP256K1 as SECP ;
426+
427+ if value < 0 {
428+ return Err ( napi:: Error :: from_reason ( "value must be non-negative" ) ) ;
429+ }
430+
431+ let pubkey: [ u8 ; 33 ] = recipient_pubkey
432+ . as_ref ( )
433+ . try_into ( )
434+ . map_err ( |_| napi:: Error :: from_reason ( "recipient_pubkey must be 33 bytes" ) ) ?;
435+ let tuid: [ u8 ; 32 ] = token_uid
436+ . as_ref ( )
437+ . try_into ( )
438+ . map_err ( |_| napi:: Error :: from_reason ( "token_uid must be 32 bytes" ) ) ?;
439+ let vbf: [ u8 ; 32 ] = value_blinding_factor
440+ . as_ref ( )
441+ . try_into ( )
442+ . map_err ( |_| napi:: Error :: from_reason ( "value_blinding_factor must be 32 bytes" ) ) ?;
443+ let abf: [ u8 ; 32 ] = asset_blinding_factor
444+ . as_ref ( )
445+ . try_into ( )
446+ . map_err ( |_| napi:: Error :: from_reason ( "asset_blinding_factor must be 32 bytes" ) ) ?;
447+
448+ // 1. Generate ephemeral keypair
449+ let ( eph_sk, eph_pk) = SECP . generate_keypair ( & mut rand:: thread_rng ( ) ) ;
450+
451+ // 2. ECDH shared secret
452+ let shared_secret = crate :: ecdh:: derive_ecdh_shared_secret (
453+ & eph_sk. secret_bytes ( ) ,
454+ & pubkey,
455+ )
456+ . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
457+
458+ // 3. Derive rewind nonce
459+ let nonce = crate :: ecdh:: derive_rewind_nonce ( & shared_secret) ;
460+
461+ // 4. Create blinded asset commitment using provided abf
462+ let tag = crate :: generators:: derive_tag ( & tuid) . map_err ( to_napi_err) ?;
463+ let abf_tweak = parse_tweak ( & abf) ?;
464+ let asset_comm = crate :: generators:: create_asset_commitment ( & tag, & abf_tweak)
465+ . map_err ( to_napi_err) ?;
466+ let ac_bytes = asset_comm. serialize ( ) ;
467+
468+ // 5. Create commitment and range proof with provided vbf
469+ let nonce_sk = secp256k1_zkp:: SecretKey :: from_slice ( & nonce)
470+ . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
471+ let bf_tweak = parse_tweak ( & vbf) ?;
472+ let comm = crate :: pedersen:: create_commitment ( value as u64 , & bf_tweak, & asset_comm)
473+ . map_err ( to_napi_err) ?;
474+ let proof = crate :: rangeproof:: create_range_proof (
475+ value as u64 , & bf_tweak, & comm, & asset_comm, None , Some ( & nonce_sk) ,
476+ )
477+ . map_err ( to_napi_err) ?;
478+
479+ Ok ( CreatedShieldedOutput {
480+ ephemeral_pubkey : Buffer :: from ( eph_pk. serialize ( ) . to_vec ( ) ) ,
481+ commitment : Buffer :: from ( comm. serialize ( ) . to_vec ( ) ) ,
482+ range_proof : Buffer :: from ( proof. serialize ( ) ) ,
483+ blinding_factor : Buffer :: from ( vbf. to_vec ( ) ) ,
484+ asset_commitment : Some ( Buffer :: from ( ac_bytes. to_vec ( ) ) ) ,
485+ asset_blinding_factor : Some ( Buffer :: from ( abf. to_vec ( ) ) ) ,
486+ } )
487+ }
0 commit comments