@@ -42,6 +42,7 @@ pub enum ProprietaryKeySubtype {
4242 Musig2PartialSig = 0x03 ,
4343 PayGoAddressAttestationProof = 0x04 ,
4444 Bip322Message = 0x05 ,
45+ WasmUtxoVersion = 0x06 ,
4546}
4647
4748impl ProprietaryKeySubtype {
@@ -53,6 +54,7 @@ impl ProprietaryKeySubtype {
5354 0x03 => Some ( ProprietaryKeySubtype :: Musig2PartialSig ) ,
5455 0x04 => Some ( ProprietaryKeySubtype :: PayGoAddressAttestationProof ) ,
5556 0x05 => Some ( ProprietaryKeySubtype :: Bip322Message ) ,
57+ 0x06 => Some ( ProprietaryKeySubtype :: WasmUtxoVersion ) ,
5658 _ => None ,
5759 }
5860 }
@@ -128,6 +130,85 @@ pub fn is_musig2_key(key: &ProprietaryKey) -> bool {
128130 )
129131}
130132
133+ /// Version information for wasm-utxo operations on PSBTs
134+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
135+ pub struct WasmUtxoVersionInfo {
136+ pub version : String ,
137+ pub git_hash : String ,
138+ }
139+
140+ impl WasmUtxoVersionInfo {
141+ /// Create a new version info structure
142+ pub fn new ( version : String , git_hash : String ) -> Self {
143+ Self { version, git_hash }
144+ }
145+
146+ /// Get the version info from compile-time constants
147+ /// Falls back to "unknown" if build.rs hasn't set the environment variables
148+ pub fn from_build_info ( ) -> Self {
149+ Self {
150+ version : option_env ! ( "WASM_UTXO_VERSION" )
151+ . unwrap_or ( "unknown" )
152+ . to_string ( ) ,
153+ git_hash : option_env ! ( "WASM_UTXO_GIT_HASH" )
154+ . unwrap_or ( "unknown" )
155+ . to_string ( ) ,
156+ }
157+ }
158+
159+ /// Serialize to bytes for proprietary key-value storage
160+ /// Format: <version_len: u8><version_bytes><git_hash_bytes (40 hex chars)>
161+ pub fn to_bytes ( & self ) -> Vec < u8 > {
162+ let mut bytes = Vec :: new ( ) ;
163+ let version_bytes = self . version . as_bytes ( ) ;
164+ bytes. push ( version_bytes. len ( ) as u8 ) ;
165+ bytes. extend_from_slice ( version_bytes) ;
166+ bytes. extend_from_slice ( self . git_hash . as_bytes ( ) ) ;
167+ bytes
168+ }
169+
170+ /// Deserialize from bytes
171+ pub fn from_bytes ( bytes : & [ u8 ] ) -> Result < Self , String > {
172+ if bytes. is_empty ( ) {
173+ return Err ( "Empty version info bytes" . to_string ( ) ) ;
174+ }
175+
176+ let version_len = bytes[ 0 ] as usize ;
177+ if bytes. len ( ) < 1 + version_len {
178+ return Err ( "Invalid version info: not enough bytes for version" . to_string ( ) ) ;
179+ }
180+
181+ let version = String :: from_utf8 ( bytes[ 1 ..1 + version_len] . to_vec ( ) )
182+ . map_err ( |e| format ! ( "Invalid UTF-8 in version: {}" , e) ) ?;
183+
184+ let git_hash = String :: from_utf8 ( bytes[ 1 + version_len..] . to_vec ( ) )
185+ . map_err ( |e| format ! ( "Invalid UTF-8 in git hash: {}" , e) ) ?;
186+
187+ Ok ( Self { version, git_hash } )
188+ }
189+
190+ /// Convert to proprietary key-value pair for PSBT global fields
191+ pub fn to_proprietary_kv ( & self ) -> ( ProprietaryKey , Vec < u8 > ) {
192+ let key = ProprietaryKey {
193+ prefix : BITGO . to_vec ( ) ,
194+ subtype : ProprietaryKeySubtype :: WasmUtxoVersion as u8 ,
195+ key : vec ! [ ] , // Empty key data - only one version per PSBT
196+ } ;
197+ ( key, self . to_bytes ( ) )
198+ }
199+
200+ /// Create from proprietary key-value pair
201+ pub fn from_proprietary_kv ( key : & ProprietaryKey , value : & [ u8 ] ) -> Result < Self , String > {
202+ if key. prefix . as_slice ( ) != BITGO {
203+ return Err ( "Not a BITGO proprietary key" . to_string ( ) ) ;
204+ }
205+ if key. subtype != ProprietaryKeySubtype :: WasmUtxoVersion as u8 {
206+ return Err ( "Not a WasmUtxoVersion proprietary key" . to_string ( ) ) ;
207+ }
208+ Self :: from_bytes ( value)
209+ }
210+ }
211+
131212/// Extract Zcash consensus branch ID from PSBT global proprietary map.
132213///
133214/// The consensus branch ID is stored as a 4-byte little-endian u32 value
@@ -239,4 +320,30 @@ mod tests {
239320 assert_eq ! ( NetworkUpgrade :: Nu5 . branch_id( ) , 0xc2d6d0b4 ) ;
240321 assert_eq ! ( NetworkUpgrade :: Nu6 . branch_id( ) , 0xc8e71055 ) ;
241322 }
323+
324+ #[ test]
325+ fn test_version_info_serialization ( ) {
326+ let version_info =
327+ WasmUtxoVersionInfo :: new ( "0.0.2" . to_string ( ) , "abc123def456" . to_string ( ) ) ;
328+
329+ let bytes = version_info. to_bytes ( ) ;
330+ let deserialized = WasmUtxoVersionInfo :: from_bytes ( & bytes) . unwrap ( ) ;
331+
332+ assert_eq ! ( deserialized, version_info) ;
333+ }
334+
335+ #[ test]
336+ fn test_version_info_proprietary_kv ( ) {
337+ let version_info =
338+ WasmUtxoVersionInfo :: new ( "0.0.2" . to_string ( ) , "abc123def456" . to_string ( ) ) ;
339+
340+ let ( key, value) = version_info. to_proprietary_kv ( ) ;
341+ assert_eq ! ( key. prefix, b"BITGO" ) ;
342+ assert_eq ! ( key. subtype, ProprietaryKeySubtype :: WasmUtxoVersion as u8 ) ;
343+ let empty_vec: Vec < u8 > = vec ! [ ] ;
344+ assert_eq ! ( key. key, empty_vec) ;
345+
346+ let deserialized = WasmUtxoVersionInfo :: from_proprietary_kv ( & key, & value) . unwrap ( ) ;
347+ assert_eq ! ( deserialized, version_info) ;
348+ }
242349}
0 commit comments