package bitcoinml

  1. Overview
  2. Docs

Source file tx.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
open Stdint;;
open Bitstring;;
open Varint;;

let amount_check ?(max_money=2100000000000000L)  am = am >= Int64.zero && am <= max_money;;

module In = struct
	type t = {
		out_hash			: string;
		out_n					: uint32;
		script				: Script.t;
		witness_script: Script.data list option;
		sequence			: uint32;
	};;

	let has_witness txin = match txin.witness_script with
	| None -> false
	| Some (wl) -> true
	;;

	let serialize txin =
		let out = Bitstring.string_of_bitstring ([%bitstring {|
			Hash.to_bin txin.out_hash : 32 * 8 : string;
			Uint32.to_int32 txin.out_n : 32 : littleendian
		|}]) in
		let sclen = string_of_bitstring (Varint.bitstring_of_varint (Int64.of_int (Script.length txin.script))) in
		let sequence = Bitstring.string_of_bitstring ([%bitstring {| Uint32.to_int32 txin.sequence : 32 : littleendian |}]) in
		out ^ sclen ^ Script.serialize (txin.script) ^ sequence
	;;

	let serialize_all txins =
		let rec serialize_all' ins = match ins with
		| [] -> ""
		| i::ins' -> (serialize i) ^ (serialize_all' ins')
		in
		let len = string_of_bitstring (Varint.bitstring_of_varint (Int64.of_int (List.length txins))) in
		len ^ (serialize_all' txins)
	;;

	let parse ?(coinbase=false) bdata =
		match%bitstring bdata with
		| {|
			out_hash	: 32*8: string;
			out_n		: 32 : littleendian;
			rest		: -1 : bitstring
		|} ->
			let sclen, rest' = parse_varint rest in
			match%bitstring rest' with
			| {|
				script 		: Uint64.to_int (sclen) * 8 : string;
				sequence	: 32 : littleendian;
				rest'		: -1 : bitstring
			|} ->
				let sc = match coinbase with
					| true -> Script.parse_coinbase script
					| false -> Script.parse script
				in

				(rest', Some ({
					out_hash= Hash.of_bin out_hash;
					out_n= Uint32.of_int32 out_n;
					script= sc;
					sequence= Uint32.of_int32 sequence;
					witness_script= None;
					}))

		| {| _ |} -> (bitstring_of_string "", None)
	;;

	let parse_all ?(coinbase=false) data =
		let inlen, rest' = parse_varint data in
		let rec parse_all' n d acc = match n with
		| 0 -> (d, Some (acc))
		| n ->
			let rest, txin = parse ~coinbase:coinbase d in
			match txin with
			| None -> (bitstring_of_string "", None)
			| Some (txin) -> parse_all' (n-1) rest (txin::acc)
		in parse_all' (Uint64.to_int inlen) rest' []
	;;
end

module Out = struct
	type t = {
		value	: int64;
		script	: Script.t;
	};;

	let is_spendable txout = Script_verify.is_spendable txout.script;;

	let spendable_by txout prefix = Script_verify.spendable_by txout.script prefix;;

	let serialize txout =
		let value = Bitstring.string_of_bitstring ([%bitstring {| txout.value : 64 : littleendian |}]) in
		let sclen = string_of_bitstring @@ Varint.bitstring_of_varint (Int64.of_int (Script.length txout.script)) in
		let sc = Script.serialize (txout.script) in
		value ^ sclen ^ sc
	;;

	let serialize_all txouts =
		let rec serialize_all' outs = match outs with
		| [] -> ""
		| o::outs' -> (serialize o) ^ (serialize_all' outs')
		in
		let len = string_of_bitstring @@ Varint.bitstring_of_varint (Int64.of_int (List.length txouts)) in
		len ^ (serialize_all' txouts)
	;;

	let parse bdata =
		match%bitstring bdata with
		| {|
			value		: 64 : littleendian;
			rest		: -1 : bitstring
		|} ->
			let sclen, rest' = parse_varint rest in
			match%bitstring rest' with
			| {|
				script 		: Uint64.to_int (sclen) * 8 : string;
				rest''		: -1 : bitstring
			|} ->
			let sc = Script.parse script in
			(match amount_check value with
			| true -> (rest'', Some ({ value= value; script= sc; }))
			| false -> (rest'', None))
		| {| _ |} -> (bitstring_of_string "", None)
	;;


	let parse_all data =
		let outlen, rest' = parse_varint data in
		let rec parse_all' n d acc = match n with
		| 0 -> (d, Some (acc))
		| n ->
			let rest, txout = parse d in
			match txout with
			| None -> (bitstring_of_string "", None)
			| Some (txout) -> parse_all' (n-1) rest (txout::acc)
		in parse_all' (Uint64.to_int outlen) rest' []
	;;
end

module Witness = struct 
	type t = {
		hash		: Hash.t;
		marker	: int;
		flag		: int;
		size		: int;
	}

	let rec serialize_fields ins = match ins with 
	| [] -> ""
	| i :: ins' -> match i.In.witness_script with
		| None -> serialize_fields ins'
		| Some (ws) ->
			let rec serws ws' = match ws' with
			| [] -> ""
			| si :: ws'' ->
				(string_of_bitstring @@ Varint.bitstring_of_varint (Int64.of_int (String.length si)))
				^ si 
				^ serws ws''
			in
			(string_of_bitstring @@ Varint.bitstring_of_varint (Int64.of_int (List.length ws)))
			^	serws ws
			^ serialize_fields ins'
	;;

	let parse_fields data n = 
		let rec pfields d n acc =
			let parse_field d =
				let wflen, rest' = parse_varint d in

				let rec pfs rest' n acc = match n with
				| 0 -> (rest', List.rev acc)
				| n ->
					let wslen, rest' = parse_varint rest' in
					match%bitstring rest' with
					| {|
						stitem : Uint64.to_int wslen * 8 : bitstring;
						rest'' : -1 : bitstring
					|} ->
						pfs rest'' (n-1) ((string_of_bitstring stitem) :: acc)
				in pfs rest' (Uint64.to_int wflen) []
			in 
			match n with
			| 0 -> (d, acc)
			| n ->
				let rest, items = parse_field d in
				pfields rest (n-1) (acc @ [items]);
		in 
			let rest, litems = pfields data n [] in
			(rest, Some (litems));;
end



type t = {
	hash			: Hash.t;
	version		: int32;
	txin 			: In.t list;
	txout 		: Out.t list;
	locktime	: uint32;
	size			: int;
	vsize			: int;
	witness		: Witness.t option;
};;


let parse_legacy ?(coinbase=false) data =
	let bdata = bitstring_of_string data in
	match%bitstring bdata with
	| {|
		version		: 32 : littleendian;
		rest		: -1 : bitstring
	|} ->
		let rest', txin = In.parse_all ~coinbase:coinbase rest in
		let rest'', txout = Out.parse_all rest' in
		match (txin, txout) with
		| None, None -> ("", None)
		| None, Some (txout) -> ("", None)
		| Some (txout), None -> ("", None)
		| Some (txin), Some (txout) ->
			let bdata = rest'' in
			match%bitstring bdata with
			| {|
				locktime	: 32 : littleendian;
				rest		: -1 : bitstring
			|} -> 
				let rest''' = string_of_bitstring rest in
				let txlen = (String.length data) - (String.length rest''') in
				let txhash = Hash.of_bin (Hash.hash256 (String.sub data 0 txlen)) in
				(rest''', Some ({
					hash	= txhash;
					version	= version;
					txin	= List.rev txin;
					txout	= List.rev txout;
					locktime= Uint32.of_int32 locktime;
					size= txlen;
					vsize= txlen;
					witness= None;
				}))
			| {| _ |} -> ("", None)
;;

let parse ?(coinbase=false) data = match coinbase with
| true -> parse_legacy ~coinbase:true data
| false -> 
	let bdata = bitstring_of_string data in
	match%bitstring bdata with
	| {|
		version		: 32 : littleendian;
		marker		: 8 : littleendian;
		flag			: 8 : littleendian;
		rest			: -1: bitstring
	|} ->
		if marker <> 0x00 || flag <> 0x01 then parse_legacy ~coinbase:false data
		else 
			let rest', txin = In.parse_all rest in
			let rest'', txout = Out.parse_all rest' in
			match (txin, txout) with
			| None, None -> ("", None)
			| None, Some (txout) -> ("", None)
			| Some (txout), None -> ("", None)
			| Some (txin), Some (txout) ->
				let rest''', fields = Witness.parse_fields rest'' @@ List.length txin in 
				match fields with | None -> ("", None) | Some (fields') ->
				let witlen = (String.length @@ string_of_bitstring rest'') - (String.length @@ string_of_bitstring rest''') + 2 in
				match%bitstring rest''' with
				| {|
					locktime	: 32 : littleendian;
					rest		: -1 : bitstring
				|} -> 
					let rest''' = string_of_bitstring rest in
					let txlen = (String.length data) - (String.length rest''') in
					let vsize = int_of_float @@ ceil ((3. *. float_of_int (txlen - witlen) +. float_of_int txlen) /. 4.) in
					let withash = Hash.of_bin (Hash.hash256 (String.sub data 0 txlen)) in
					let txhash = Hash.of_bin @@ Hash.dsha256 (
						Bitstring.string_of_bitstring ([%bitstring {| version : 32 : littleendian |}]) 
						^ (In.serialize_all txin) 
						^ (Out.serialize_all txout)
						^ Bitstring.string_of_bitstring ([%bitstring {| locktime : 32 : littleendian |}])) in
					let txin' = List.mapi (fun j tin -> { tin with In.witness_script= Some (List.nth fields' j) }) (List.rev txin) in
					(rest''', Some ({
						hash= txhash;
						version	= version;
						txin	= txin';
						txout	= List.rev txout;
						locktime= Uint32.of_int32 locktime;
						size= txlen - witlen;
						vsize= vsize;
						witness= Some ({ 
							hash= withash;
							marker= marker;
							flag= flag;
							size= witlen;
						});
					}))
				| {| _ |} -> ("", None)
;;


let serialize_legacy tx =
	Bitstring.string_of_bitstring ([%bitstring {| tx.version : 32 : littleendian |}]) 
	^ (In.serialize_all tx.txin) 
	^ (Out.serialize_all tx.txout)
	^ Bitstring.string_of_bitstring ([%bitstring {| Uint32.to_int32 tx.locktime : 32 : littleendian |}])
;;

let serialize tx = match tx.witness with
| None -> serialize_legacy tx
| Some (w) -> 
	Bitstring.string_of_bitstring ([%bitstring {| tx.version : 32 : littleendian |}])
	^ Bitstring.string_of_bitstring ([%bitstring {| w.marker : 8 : littleendian |}])
	^ Bitstring.string_of_bitstring ([%bitstring {| w.flag : 8 : littleendian |}])
	^ (In.serialize_all tx.txin) 
	^ (Out.serialize_all tx.txout)
	^ (Witness.serialize_fields tx.txin)
	^ Bitstring.string_of_bitstring ([%bitstring {| Uint32.to_int32 tx.locktime : 32 : littleendian |}])
;;



let rec serialize_all txs = match txs with
| [] -> ""
| tx::txs' -> (serialize tx) ^ (serialize_all txs')
;;

let rec serialize_all_legacy txs = match txs with
| [] -> ""
| tx::txs' -> (serialize_legacy tx) ^ (serialize_all_legacy txs')
;;

let parse_all data ntx =
	let rec parse_all' n d acc =
		(*Printf.printf "Loop! %d\n%!" n;*)

	match n with
	| 0 -> (*Printf.printf "End! %d\n%!" n;*) Some (acc)
	| n ->
		let rest, tx = if n = ntx then parse d ~coinbase:true else parse d in
		match tx with
		| None -> None
		| Some (mtx) ->
			let ser = serialize mtx in
			let dlen = (String.length d) in
			let rlen = (String.length rest) in
			let subs = String.sub d 0 (dlen - rlen) in

			if (subs <> ser) then None else parse_all' (n-1) rest (mtx::acc)
	in
	parse_all' ntx data []
;;


let parse_all_legacy data ntx =
	let rec parse_all' n d acc =
		(*Printf.printf "Loop! %d\n%!" n;*)

	match n with
	| 0 -> (*Printf.printf "End! %d\n%!" n;*) Some (acc)
	| n ->
		let rest, tx = if n = ntx then parse_legacy d ~coinbase:true else parse_legacy d in
		match tx with
		| None -> None
		| Some (mtx) ->
			let ser = serialize_legacy mtx in
			let dlen = (String.length d) in
			let rlen = (String.length rest) in
			let subs = String.sub d 0 (dlen - rlen) in

			if (subs <> ser) then None else parse_all' (n-1) rest (mtx::acc)
	in
	parse_all' ntx data []
;;



let is_witness tx = match tx.witness with
| None -> false
| Some (w) -> true
;;


let is_coinbase tx = 
	if List.length tx.txin <> 1 then false
	else
		match (List.nth tx.txin 0) with
		| i when i.out_hash = Hash.zero && i.out_n = (Uint32.sub Uint32.zero Uint32.one) -> (match i.script with
			| ([OP_COINBASE (s)], l) when l >= 2 && l <= 100 -> true
			| _ -> false)
		| _ -> false
;;