@@ -3,6 +3,32 @@ var bs58check = require('bs58check');
33var zcrypto = require ( './crypto' ) ;
44var btcmessage = require ( './message' ) ;
55
6+ // FluxNode transaction version constants
7+ const FLUXNODE_TX_VERSION = 5 ;
8+ const FLUXNODE_TX_UPGRADEABLE_VERSION = 6 ;
9+
10+ // Legacy version constants for backward compatibility
11+ const FLUXNODE_INTERNAL_NORMAL_TX_VERSION = 1 ;
12+ const FLUXNODE_INTERNAL_P2SH_TX_VERSION = 2 ;
13+
14+ // Bit-based version system
15+ // Bits 0-7: Transaction type
16+ const FLUXNODE_TX_TYPE_MASK = 0xFF ;
17+ const FLUXNODE_TX_TYPE_NORMAL_BIT = 0x01 ; // Bit 0
18+ const FLUXNODE_TX_TYPE_P2SH_BIT = 0x02 ; // Bit 1
19+
20+ // Bits 8-15: Feature flags
21+ const FLUXNODE_TX_FEATURE_MASK = 0xFF00 ;
22+ const FLUXNODE_TX_FEATURE_DELEGATES_BIT = 0x0100 ; // Bit 8
23+
24+ // Delegate type constants
25+ const DELEGATE_TYPE_NONE = 0 ;
26+ const DELEGATE_TYPE_UPDATE = 1 ;
27+ const DELEGATE_TYPE_SIGNING = 2 ;
28+
29+ const DELEGATE_VERSION_INITIAL = 1 ;
30+ const MAX_DELEGATE_PUBKEYS = 4 ;
31+
632function doubleHash ( msg ) {
733 const bufMessage = Buffer . from ( msg , 'hex' )
834 message = zcrypto . sha256x2 ( bufMessage ) . toString ( 'hex' ) ;
@@ -65,10 +91,10 @@ function signStartMessage(message, privKey, compressed = true) {
6591 return mysignature . toString ( "hex" ) ;
6692}
6793
68- function getZelNodePublicKey ( zelnodePrivateKey ) {
94+ function getZelNodePublicKey ( zelnodePrivateKey , compressed = false ) {
6995 var og = WIFToPrivKey ( zelnodePrivateKey ) ;
7096
71- toCompressed = false ;
97+ var toCompressed = compressed ;
7298
7399 const pkBuffer = Buffer . from ( og , 'hex' ) ;
74100 var publicKey = secp256k1 . publicKeyCreate ( pkBuffer , toCompressed ) ;
@@ -86,12 +112,81 @@ function getCollateralPublicKey(collateralPrivateKey, compressed = true) {
86112 return publicKey . toString ( 'hex' ) ;
87113}
88114
115+ // Helper function to get public key from private key
116+ function getPublicKeyFromPrivateKey ( privateKey , compressed = true ) {
117+ var og = WIFToPrivKey ( privateKey ) ;
118+ const pkBuffer = Buffer . from ( og , 'hex' ) ;
119+ var publicKey = secp256k1 . publicKeyCreate ( pkBuffer , compressed ) ;
120+ return publicKey . toString ( 'hex' ) ;
121+ }
122+
89123// reverse hex string byte order
90124function reverseHex ( hex ) {
91125 const buf = Buffer . from ( hex , 'hex' ) . reverse ( ) ;
92126 return buf . toString ( 'hex' ) ;
93127}
94128
129+ // Helper functions for version checking
130+ function hasConflictingBits ( version ) {
131+ return ( version & FLUXNODE_TX_TYPE_NORMAL_BIT ) !== 0 && ( version & FLUXNODE_TX_TYPE_P2SH_BIT ) !== 0 ;
132+ }
133+
134+ function isFluxTxNormalType ( version ) {
135+ if ( hasConflictingBits ( version ) ) {
136+ return false ;
137+ }
138+ return ( version & FLUXNODE_TX_TYPE_NORMAL_BIT ) !== 0 || version === FLUXNODE_INTERNAL_NORMAL_TX_VERSION ;
139+ }
140+
141+ function isFluxTxP2SHType ( version ) {
142+ if ( hasConflictingBits ( version ) ) {
143+ return false ;
144+ }
145+ return ( version & FLUXNODE_TX_TYPE_P2SH_BIT ) !== 0 || version === FLUXNODE_INTERNAL_P2SH_TX_VERSION ;
146+ }
147+
148+ function hasFluxTxDelegatesFeature ( version ) {
149+ return ( version & FLUXNODE_TX_FEATURE_DELEGATES_BIT ) !== 0 ;
150+ }
151+
152+ // Serialize delegate data
153+ function serializeDelegateData ( delegateData ) {
154+ var serialized = '' ;
155+
156+ // nDelegateVersion (1 byte)
157+ var versionBuf = Buffer . alloc ( 1 ) ;
158+ versionBuf . writeUInt8 ( delegateData . version || DELEGATE_VERSION_INITIAL ) ;
159+ serialized += versionBuf . toString ( 'hex' ) ;
160+
161+ // nType (1 byte)
162+ var typeBuf = Buffer . alloc ( 1 ) ;
163+ typeBuf . writeUInt8 ( delegateData . type ) ;
164+ serialized += typeBuf . toString ( 'hex' ) ;
165+
166+ // If type is UPDATE, serialize the delegate public keys vector
167+ if ( delegateData . type === DELEGATE_TYPE_UPDATE && delegateData . delegatePublicKeys ) {
168+ // Number of keys (varint) - this is how std::vector is serialized
169+ var numKeys = delegateData . delegatePublicKeys . length ;
170+ if ( numKeys > MAX_DELEGATE_PUBKEYS ) {
171+ throw new Error ( 'Too many delegate public keys. Maximum is ' + MAX_DELEGATE_PUBKEYS ) ;
172+ }
173+
174+ var numKeysVarint = varintBufNum ( numKeys ) ;
175+ serialized += numKeysVarint . toString ( 'hex' ) ;
176+
177+ // Each CPubKey in the vector
178+ for ( var i = 0 ; i < delegateData . delegatePublicKeys . length ; i ++ ) {
179+ var pubKey = delegateData . delegatePublicKeys [ i ] ;
180+ // CPubKey serialization uses CompactSize for length prefix
181+ var pubKeyLength = varintBufNum ( pubKey . length / 2 ) ;
182+ serialized += pubKeyLength . toString ( 'hex' ) ;
183+ serialized += pubKey ;
184+ }
185+ }
186+
187+ return serialized ;
188+ }
189+
95190// zelnodePrivateKey as WIF formate, collateralPrivateKey as WIF, timestamp in seconds
96191function startZelNode ( collateralOutHash , collateralOutIndex , collateralPrivateKey , zelnodePrivateKey , timestamp , compressedCollateralPrivateKey = true ) {
97192 // it is up to wallet to find out collateral Public Key.
@@ -157,6 +252,233 @@ function startZelNode(collateralOutHash, collateralOutIndex, collateralPrivateKe
157252 return serializedTx ;
158253}
159254
255+ // FluxNode v6 start with optional delegate support and P2SH
256+ function startFluxNodev6 ( collateralOutHash , collateralOutIndex , collateralPrivateKey , fluxnodePrivateKey , timestamp , compressedCollateralPrivateKey = true , compressedFluxnodePrivateKey = false , redeemScript , delegateData = null ) {
257+ const version = 6 ;
258+ const nType = 2 ;
259+
260+ const FLUXNODE_NORMAL_TX_VERSION = 1 ;
261+ const FLUXNODE_P2SH_TX_VERSION = 2 ;
262+
263+ var nFluxNodeTxVersion = FLUXNODE_NORMAL_TX_VERSION ;
264+ if ( redeemScript ) {
265+ nFluxNodeTxVersion = FLUXNODE_P2SH_TX_VERSION ;
266+ }
267+
268+ // Add delegate feature bit if delegate data is provided
269+ if ( delegateData ) {
270+ nFluxNodeTxVersion |= FLUXNODE_TX_FEATURE_DELEGATES_BIT ;
271+ }
272+
273+ var serializedTx = '' ;
274+
275+ // Version
276+ var _buf32 = Buffer . alloc ( 4 ) ;
277+ _buf32 . writeUInt32LE ( version ) ;
278+ serializedTx += _buf32 . toString ( 'hex' ) ;
279+
280+ // nType
281+ var _buf8 = Buffer . alloc ( 1 ) ;
282+ _buf8 . writeUInt8 ( nType ) ;
283+ serializedTx += _buf8 . toString ( 'hex' ) ;
284+
285+ // nFluxNodeTxVersion
286+ var _buf32A = Buffer . alloc ( 4 ) ;
287+ _buf32A . writeUInt32LE ( nFluxNodeTxVersion , 0 ) ;
288+ serializedTx += _buf32A . toString ( 'hex' ) ;
289+
290+ // collateralOutHash
291+ serializedTx += reverseHex ( collateralOutHash ) ;
292+
293+ // collateralOutIndex
294+ var _buf32B = Buffer . alloc ( 4 ) ;
295+ _buf32B . writeUInt32LE ( collateralOutIndex , 0 ) ;
296+ serializedTx += _buf32B . toString ( 'hex' ) ;
297+
298+ // Check if this is a normal tx (not P2SH)
299+ var isNormalTx = isFluxTxNormalType ( nFluxNodeTxVersion ) && ! ( nFluxNodeTxVersion & FLUXNODE_TX_TYPE_P2SH_BIT ) ;
300+ var isP2SHTx = isFluxTxP2SHType ( nFluxNodeTxVersion ) ;
301+
302+ if ( isNormalTx ) {
303+ // get collateral public key
304+ var collateralPublicKey = getCollateralPublicKey ( collateralPrivateKey , compressedCollateralPrivateKey ) ;
305+
306+ // collateralPublicKeyLength
307+ var pubKeyLength = varintBufNum ( collateralPublicKey . length / 2 ) ;
308+ serializedTx += pubKeyLength . toString ( 'hex' ) ;
309+
310+ // collateralPublicKey
311+ serializedTx += collateralPublicKey ;
312+ }
313+
314+ // get public key from fluxnode private key;
315+ var fluxnodePublicKey = getZelNodePublicKey ( fluxnodePrivateKey , compressedFluxnodePrivateKey ) ;
316+
317+ // fluxnodePublicKeyLength
318+ var fnPubKeyLength = varintBufNum ( fluxnodePublicKey . length / 2 ) ;
319+ serializedTx += fnPubKeyLength . toString ( 'hex' ) ;
320+
321+ // fluxnodePublicKey
322+ serializedTx += fluxnodePublicKey ;
323+
324+ if ( isP2SHTx ) {
325+ // redeemScriptLength
326+ var redeemScriptLength = varintBufNum ( redeemScript . length / 2 ) ;
327+ serializedTx += redeemScriptLength . toString ( 'hex' ) ;
328+
329+ // redeemScript
330+ serializedTx += redeemScript ;
331+ }
332+
333+ // timestamp
334+ var _buf32C = Buffer . alloc ( 4 ) ;
335+ _buf32C . writeUInt32LE ( timestamp ) ;
336+ serializedTx += _buf32C . toString ( 'hex' ) ;
337+
338+ // Build transaction for signing (includes delegate data but not signature)
339+ var txForSigning = serializedTx ;
340+
341+ // Add delegate data to the transaction we're going to sign
342+ if ( hasFluxTxDelegatesFeature ( nFluxNodeTxVersion ) ) {
343+ // fUsingDelegates (1 byte boolean)
344+ var usingDelegates = delegateData !== null ;
345+ var delegateBoolBuf = Buffer . alloc ( 1 ) ;
346+ delegateBoolBuf . writeUInt8 ( usingDelegates ? 1 : 0 ) ;
347+ txForSigning += delegateBoolBuf . toString ( 'hex' ) ;
348+
349+ if ( usingDelegates ) {
350+ // Serialize the delegate data
351+ txForSigning += serializeDelegateData ( delegateData ) ;
352+ }
353+ }
354+
355+ // Sign the transaction (including delegate data)
356+ var signature = signStartMessage ( txForSigning , collateralPrivateKey , compressedCollateralPrivateKey ) ;
357+
358+ // Now build the final transaction with signature in the right place
359+ // Add signature BEFORE delegate data (matches C++ serialization order)
360+ var sigLength = varintBufNum ( signature . length / 2 ) ;
361+ serializedTx += sigLength . toString ( 'hex' ) ;
362+ serializedTx += signature ;
363+
364+ // Then add delegate data after signature
365+ if ( hasFluxTxDelegatesFeature ( nFluxNodeTxVersion ) ) {
366+ // fUsingDelegates (1 byte boolean)
367+ var usingDelegates = delegateData !== null ;
368+ var delegateBoolBuf = Buffer . alloc ( 1 ) ;
369+ delegateBoolBuf . writeUInt8 ( usingDelegates ? 1 : 0 ) ;
370+ serializedTx += delegateBoolBuf . toString ( 'hex' ) ;
371+
372+ if ( usingDelegates ) {
373+ // Serialize the delegate data
374+ serializedTx += serializeDelegateData ( delegateData ) ;
375+ }
376+ }
377+
378+ return serializedTx ;
379+ }
380+
381+ // Function to start FluxNode and add delegates (owner adding delegate permissions)
382+ function startFluxNodeAddDelegate ( collateralOutHash , collateralOutIndex , collateralPrivateKey , fluxnodePrivateKey , timestamp , delegatePublicKeys , compressedCollateralPrivateKey = true , compressedFluxnodePrivateKey = false , redeemScript = null ) {
383+ // Validate delegate public keys
384+ if ( ! delegatePublicKeys || ! Array . isArray ( delegatePublicKeys ) ) {
385+ throw new Error ( 'delegatePublicKeys array is required for startFluxNodeAddDelegate' ) ;
386+ }
387+
388+ if ( delegatePublicKeys . length === 0 ) {
389+ throw new Error ( 'At least one delegate public key must be provided' ) ;
390+ }
391+
392+ if ( delegatePublicKeys . length > MAX_DELEGATE_PUBKEYS ) {
393+ throw new Error ( 'Too many delegate public keys. Maximum is ' + MAX_DELEGATE_PUBKEYS ) ;
394+ }
395+
396+ // Validate each public key
397+ for ( var i = 0 ; i < delegatePublicKeys . length ; i ++ ) {
398+ var pubKey = delegatePublicKeys [ i ] ;
399+ if ( typeof pubKey !== 'string' || pubKey . length !== 66 ) {
400+ throw new Error ( 'Invalid delegate public key format. Must be 33 bytes (66 hex chars) compressed public key' ) ;
401+ }
402+ }
403+
404+ // Create delegate data for UPDATE type
405+ var delegateData = {
406+ version : DELEGATE_VERSION_INITIAL ,
407+ type : DELEGATE_TYPE_UPDATE ,
408+ delegatePublicKeys : delegatePublicKeys ,
409+ } ;
410+
411+ // Use v6 with delegate data
412+ return startFluxNodev6 (
413+ collateralOutHash ,
414+ collateralOutIndex ,
415+ collateralPrivateKey ,
416+ fluxnodePrivateKey ,
417+ timestamp ,
418+ compressedCollateralPrivateKey ,
419+ compressedFluxnodePrivateKey ,
420+ redeemScript ,
421+ delegateData
422+ ) ;
423+ }
424+
425+ // Function for delegates to start a FluxNode (delegate using their permission)
426+ function startFluxNodeAsDelegate ( collateralOutHash , collateralOutIndex , delegatePrivateKey , fluxnodePrivateKey , timestamp , compressedDelegatePrivateKey = true , compressedFluxnodePrivateKey = false , redeemScript = null ) {
427+ // Create delegate data for SIGNING type
428+ var delegateData = {
429+ version : DELEGATE_VERSION_INITIAL ,
430+ type : DELEGATE_TYPE_SIGNING ,
431+ } ;
432+
433+ // Note: When signing as a delegate, we use the delegate's private key instead of collateral private key
434+ // Use v6 with delegate data
435+ return startFluxNodev6 (
436+ collateralOutHash ,
437+ collateralOutIndex ,
438+ delegatePrivateKey , // Using delegate's private key for signing
439+ fluxnodePrivateKey ,
440+ timestamp ,
441+ compressedDelegatePrivateKey , // Compression for delegate key
442+ compressedFluxnodePrivateKey ,
443+ redeemScript ,
444+ delegateData
445+ ) ;
446+ }
447+
448+ // Helper function to create delegate data object for advanced use cases
449+ function createDelegateData ( type , delegatePublicKeys = null , delegatePrivateKeys = null ) {
450+ var delegateData = {
451+ version : DELEGATE_VERSION_INITIAL ,
452+ type : type ,
453+ } ;
454+
455+ if ( type === DELEGATE_TYPE_UPDATE ) {
456+ if ( ! delegatePublicKeys && delegatePrivateKeys ) {
457+ // Convert private keys to public keys
458+ delegateData . delegatePublicKeys = delegatePrivateKeys . map ( function ( privKey ) {
459+ return getPublicKeyFromPrivateKey ( privKey , true ) ; // Always use compressed keys for delegates
460+ } ) ;
461+ } else if ( delegatePublicKeys ) {
462+ delegateData . delegatePublicKeys = delegatePublicKeys ;
463+ } else {
464+ throw new Error ( 'Either delegatePublicKeys or delegatePrivateKeys must be provided for UPDATE type' ) ;
465+ }
466+ }
467+
468+ return delegateData ;
469+ }
470+
471+ // Helper to convert delegate private keys to public keys
472+ function convertDelegatePrivateKeysToPublic ( delegatePrivateKeys ) {
473+ if ( ! delegatePrivateKeys || ! Array . isArray ( delegatePrivateKeys ) ) {
474+ throw new Error ( 'delegatePrivateKeys must be an array' ) ;
475+ }
476+
477+ return delegatePrivateKeys . map ( function ( privKey ) {
478+ return getPublicKeyFromPrivateKey ( privKey , true ) ; // Always use compressed keys for delegates
479+ } ) ;
480+ }
481+
160482function txidStart ( rawtx ) {
161483 // signature is 41 hex length in bytes, remove last 41 -> 65 in decimal. Remove last 65+1 byte (byte before states the signature size);
162484 const adjustedRawTx = rawtx . substr ( 0 , rawtx . length - 132 ) ;
@@ -175,12 +497,26 @@ function txidConfirm(rawtx) {
175497
176498
177499module . exports = {
500+ // Legacy v5 function
178501 startZelNode,
502+ // v6 functions with delegation support
503+ startFluxNodev6,
504+ startFluxNodeAddDelegate,
505+ startFluxNodeAsDelegate,
506+ // Helper functions
507+ createDelegateData,
508+ convertDelegatePrivateKeysToPublic,
179509 getZelNodePublicKey,
180510 getCollateralPublicKey,
511+ getPublicKeyFromPrivateKey,
181512 signMessage,
182513 doubleHash,
183514 signStartMessage,
184515 txidStart,
185516 txidConfirm,
517+ // Constants
518+ DELEGATE_TYPE_NONE ,
519+ DELEGATE_TYPE_UPDATE ,
520+ DELEGATE_TYPE_SIGNING ,
521+ MAX_DELEGATE_PUBKEYS ,
186522} ;
0 commit comments