@@ -109,34 +109,181 @@ export default class FractionalizeTopicManager implements TopicManager {
109109 }
110110}
111111
112+ // Template sections for comprehensive script validation
112113const TEMPLATES = {
113- // Script hexstrings for each type (gotten from scriptHex function below)
114- "server-token" : "00630300000000000000000000000000000000000000005112000000000000000000000000000000000000000000240000000000000000000000000000000000000000686e7ea9140000000000000000000000000000000000000000886b6b516c6c52ae6a200000000000000000000000000000000000000000" ,
115- "transfer-token" : "006303000000000000000000000000000000000000000051120000000000000000000000000000000000000000002400000000000000000000000000000000000000006876a914000000000000000000000000000000000000000088ac6a200000000000000000000000000000000000000000" ,
116- "payment" : "6e7ea9140000000000000000000000000000000000000000886b6b516c6c52ae"
114+ "server-token" : {
115+ // OP_0 OP_IF 'ord' OP_1 'application/bsv-20' OP_0
116+ formatStart : '0063036f726451126170706c69636174696f6e2f6273762d323000' ,
117+ // OP_ENDIF OP_2DUP OP_CAT OP_HASH160
118+ formatMiddle : '686e7ea9' ,
119+ // OP_EQUALVERIFY OP_TOALTSTACK OP_TOALTSTACK OP_1 OP_FROMALTSTACK OP_FROMALTSTACK OP_2 OP_CHECKMULTISIG
120+ formatEnd : '886b6b516c6c52ae'
121+ } ,
122+ "transfer-token" : {
123+ // OP_0 OP_IF 'ord' OP_1 'application/bsv-20' OP_0
124+ formatStart : '0063036f726451126170706c69636174696f6e2f6273762d323000' ,
125+ // OP_ENDIF OP_DUP OP_HASH160
126+ formatMiddle : '6876a9' ,
127+ // OP_EQUALVERIFY OP_CHECKSIG
128+ formatEnd : '88ac'
129+ } ,
130+ "payment" : {
131+ // OP_2DUP OP_CAT OP_HASH160
132+ formatStart : '6e7ea9' ,
133+ // OP_EQUALVERIFY OP_TOALTSTACK OP_TOALTSTACK OP_1 OP_FROMALTSTACK OP_FROMALTSTACK OP_2 OP_CHECKMULTISIG
134+ formatEnd : '886b6b516c6c52ae'
135+ }
117136}
118- const NORMALIZED_TEMPLATES = {
119- "server-token" : Script . fromHex ( TEMPLATES [ "server-token" ] ) . toASM ( ) ,
120- "transfer-token" : Script . fromHex ( TEMPLATES [ "transfer-token" ] ) . toASM ( ) ,
121- "payment" : Script . fromHex ( TEMPLATES [ "payment" ] ) . toASM ( )
122- } ;
123137
124138function checkScriptFormat ( script : Script , type : "server-token" | "transfer-token" | "payment" ) {
125- const inputScript = Script . fromHex ( script . toHex ( ) )
139+ try {
140+ const chunks = script . chunks
126141
127- // Blank out the data fields to leave us with a standard template
128- inputScript . chunks . forEach ( chunk => {
129- if ( chunk . data && Utils . toHex ( chunk . data ) !== Utils . toHex ( [ 33 ] ) ) {
130- chunk . data = Array ( 20 ) . fill ( 0 )
131- }
132- } ) ;
142+ switch ( type ) {
143+ case "server-token" : {
144+ // Server token: ordinal inscription + multisig
145+ // Structure: formatStart (0-5) | JSON (6) | formatMiddle (7-10) | hash (11) | formatEnd (12-19) | OP_RETURN (20)
146+
147+ // Check formatStart (chunks 0-5): OP_0 OP_IF 'ord' OP_1 'application/bsv-20' OP_0
148+ const formatStart = new Script ( chunks . slice ( 0 , 6 ) ) . toHex ( )
149+ if ( formatStart !== TEMPLATES [ 'server-token' ] . formatStart ) {
150+ throw new Error ( 'Malformed formatStart' )
151+ }
152+
153+ // Validate JSON payload (chunk 6)
154+ try {
155+ const formatJsonPayload = JSON . parse ( Utils . toUTF8 ( chunks [ 6 ] . data ) )
156+ const incorrectlyFormatted =
157+ ( formatJsonPayload . p !== 'bsv-20' ) ||
158+ ! ( formatJsonPayload . op === 'transfer' || formatJsonPayload . op === 'deploy+mint' ) ||
159+ formatJsonPayload . amt === undefined ||
160+ ( formatJsonPayload . op === 'transfer' && ! formatJsonPayload . id )
161+
162+ if ( incorrectlyFormatted ) {
163+ throw new Error ( 'Malformed JSON payload' )
164+ }
165+ } catch ( error ) {
166+ throw new Error ( `Invalid JSON payload: ${ error . message } ` )
167+ }
168+
169+ // Check formatMiddle (chunks 7-10): OP_ENDIF OP_2DUP OP_CAT OP_HASH160
170+ const formatMiddle = new Script ( chunks . slice ( 7 , 11 ) ) . toHex ( )
171+ if ( formatMiddle !== TEMPLATES [ 'server-token' ] . formatMiddle ) {
172+ throw new Error ( 'Malformed formatMiddle' )
173+ }
174+
175+ // Check hash data (chunk 11): should be 20 bytes
176+ if ( ! chunks [ 11 ] . data || chunks [ 11 ] . data . length !== 20 ) {
177+ throw new Error ( 'Invalid hash data length' )
178+ }
179+
180+ // Check formatEnd (chunks 12-19): multisig ending
181+ const formatEnd = new Script ( chunks . slice ( 12 , 20 ) ) . toHex ( )
182+ if ( formatEnd !== TEMPLATES [ 'server-token' ] . formatEnd ) {
183+ throw new Error ( 'Malformed formatEnd' )
184+ }
185+
186+ // Check OP_RETURN (chunk 20)
187+ if ( chunks [ 20 ] . op !== 106 ) { // 106 = 0x6a = OP_RETURN
188+ throw new Error ( 'No OP_RETURN at the end' )
189+ }
190+
191+ // Validate OP_RETURN contains data (original mint txid)
192+ if ( ! chunks [ 20 ] . data || chunks [ 20 ] . data . length === 0 ) {
193+ throw new Error ( 'Missing OP_RETURN data' )
194+ }
195+
196+ return { valid : true , message : 'Script is valid' }
197+ }
133198
134- // Check if the script matches the template
135- // Normalize scripts to ASM to handle pushdata code differences
136- const isValid = inputScript . toASM ( ) === NORMALIZED_TEMPLATES [ type ] ;
199+ case "transfer-token" : {
200+ // Transfer token: ordinal inscription + P2PKH
201+ // Structure: formatStart (0-5) | JSON (6) | formatMiddle (7-9) | hash (10) | formatEnd (11-12) | OP_RETURN (13)
202+
203+ // Check formatStart (chunks 0-5)
204+ const formatStart = new Script ( chunks . slice ( 0 , 6 ) ) . toHex ( )
205+ if ( formatStart !== TEMPLATES [ 'transfer-token' ] . formatStart ) {
206+ throw new Error ( 'Malformed formatStart' )
207+ }
208+
209+ // Validate JSON payload (chunk 6)
210+ try {
211+ const formatJsonPayload = JSON . parse ( Utils . toUTF8 ( chunks [ 6 ] . data ) )
212+ const incorrectlyFormatted =
213+ ( formatJsonPayload . p !== 'bsv-20' ) ||
214+ ! ( formatJsonPayload . op === 'transfer' || formatJsonPayload . op === 'deploy+mint' ) ||
215+ formatJsonPayload . amt === undefined ||
216+ ( formatJsonPayload . op === 'transfer' && ! formatJsonPayload . id )
217+
218+ if ( incorrectlyFormatted ) {
219+ throw new Error ( 'Malformed JSON payload' )
220+ }
221+ } catch ( error ) {
222+ throw new Error ( `Invalid JSON payload: ${ error . message } ` )
223+ }
137224
138- return {
139- valid : isValid ,
140- message : isValid ? 'Script is valid' : 'Script is invalid'
225+ // Check formatMiddle (chunks 7-9): OP_ENDIF OP_DUP OP_HASH160
226+ const formatMiddle = new Script ( chunks . slice ( 7 , 10 ) ) . toHex ( )
227+ if ( formatMiddle !== TEMPLATES [ 'transfer-token' ] . formatMiddle ) {
228+ throw new Error ( 'Malformed formatMiddle' )
229+ }
230+
231+ // Check pubkey hash data (chunk 10): should be 20 bytes
232+ if ( ! chunks [ 10 ] . data || chunks [ 10 ] . data . length !== 20 ) {
233+ throw new Error ( 'Invalid pubkey hash data length' )
234+ }
235+
236+ // Check formatEnd (chunks 11-12): OP_EQUALVERIFY OP_CHECKSIG
237+ const formatEnd = new Script ( chunks . slice ( 11 , 13 ) ) . toHex ( )
238+ if ( formatEnd !== TEMPLATES [ 'transfer-token' ] . formatEnd ) {
239+ throw new Error ( 'Malformed formatEnd' )
240+ }
241+
242+ // Check OP_RETURN (chunk 13)
243+ if ( chunks [ 13 ] . op !== 106 ) { // 106 = 0x6a = OP_RETURN
244+ throw new Error ( 'No OP_RETURN at the end' )
245+ }
246+
247+ // Validate OP_RETURN contains data (original mint txid)
248+ if ( ! chunks [ 13 ] . data || chunks [ 13 ] . data . length === 0 ) {
249+ throw new Error ( 'Missing OP_RETURN data' )
250+ }
251+
252+ return { valid : true , message : 'Script is valid' }
253+ }
254+
255+ case "payment" : {
256+ // Payment script: just multisig, no ordinal inscription
257+ // Structure: formatStart (0-2) | hash (3) | formatEnd (4-11)
258+
259+ // Check formatStart (chunks 0-2): OP_2DUP OP_CAT OP_HASH160
260+ const formatStart = new Script ( chunks . slice ( 0 , 3 ) ) . toHex ( )
261+ if ( formatStart !== TEMPLATES [ 'payment' ] . formatStart ) {
262+ throw new Error ( 'Malformed formatStart' )
263+ }
264+
265+ // Check hash data (chunk 3): should be 20 bytes
266+ if ( ! chunks [ 3 ] . data || chunks [ 3 ] . data . length !== 20 ) {
267+ throw new Error ( 'Invalid hash data length' )
268+ }
269+
270+ // Check formatEnd (chunks 4-11): multisig ending
271+ const formatEnd = new Script ( chunks . slice ( 4 , 12 ) ) . toHex ( )
272+ if ( formatEnd !== TEMPLATES [ 'payment' ] . formatEnd ) {
273+ throw new Error ( 'Malformed formatEnd' )
274+ }
275+
276+ return { valid : true , message : 'Script is valid' }
277+ }
278+
279+ default :
280+ throw new Error ( `Unknown script type: ${ type } ` )
281+ }
282+
283+ } catch ( error ) {
284+ return {
285+ valid : false ,
286+ message : error ?. message || 'Invalid script format'
287+ }
141288 }
142289}
0 commit comments