1 module tagion.script.common;
2 @safe:
3 
4 // import std.algorithm;
5 import std.array;
6 import std.range;
7 import tagion.basic.Types;
8 import tagion.basic.Types : Buffer;
9 import tagion.crypto.SecureInterfaceNet;
10 import tagion.crypto.Types;
11 import tagion.dart.DARTBasic;
12 import tagion.hibon.BigNumber;
13 import tagion.hibon.Document;
14 import tagion.hibon.HiBONRecord;
15 import tagion.script.ScriptException;
16 import tagion.script.TagionCurrency;
17 import tagion.script.standardnames;
18 import tagion.utils.StdTime;
19 
20 @recordType("TGN") struct TagionBill {
21     @label(StdNames.value) TagionCurrency value; /// Tagion bill 
22     @label(StdNames.time) sdt_t time; // Time stamp
23     @label(StdNames.owner) Pubkey owner; // owner key
24     @label(StdNames.nonce) @optional Buffer nonce; // extra nonce 
25     mixin HiBONRecord!(
26             q{
27                 this(const(TagionCurrency) value, const sdt_t time, Pubkey owner, Buffer nonce) pure nothrow {
28                     this.value = value;
29                     this.time = time;
30                     this.owner = owner;
31                     this.nonce = nonce;
32                 }
33             });
34 }
35 
36 @recordType("SMC") struct Contract {
37     @label("$in") const(DARTIndex)[] inputs; /// Hash pointer to input (DART)
38     @label("$read") @optional @(filter.Initialized) const(DARTIndex)[] reads; /// Hash pointer to read-only input (DART)
39     @label("$run") Document script; // Smart contract 
40     bool verify() {
41         return (inputs.length > 0);
42     }
43 
44     mixin HiBONRecord!(
45             q{
46                 this(const(DARTIndex)[] inputs, const(DARTIndex)[] reads, Document script) pure nothrow {
47                     this.inputs = inputs;
48                     this.reads = reads;
49                     this.script = script; 
50                 }
51                 this(immutable(DARTIndex)[] inputs, immutable(DARTIndex)[] reads, immutable(Document) script) immutable nothrow {
52                     this.inputs = inputs;
53                     this.reads = reads;
54                     this.script = script; 
55                 }
56             });
57 }
58 
59 @recordType("SSC") struct SignedContract {
60     @label("$signs") const(Signature)[] signs; /// Signature of all inputs
61     @label("$contract") Contract contract; /// The contract must signed by all inputs
62     mixin HiBONRecord!(
63             q{
64                 this(const(Signature)[] signs, Contract contract) pure nothrow {
65                     this.signs = signs;
66                     this.contract = contract;
67                 }
68                 this(immutable(Signature)[] signs, immutable(Contract) contract) nothrow immutable {
69                     this.signs = signs;
70                     this.contract = contract;
71                 }
72                 this(const(Document) doc) immutable @trusted {
73                     immutable _this=cast(immutable)SignedContract(doc);
74                     this.signs=_this.signs;
75                     this.contract=_this.contract;
76                 }
77             });
78 }
79 
80 @recordType("pay")
81 struct PayScript {
82     @label(StdNames.values) const(TagionBill)[] outputs;
83     mixin HiBONRecord!(
84             q{
85                 this(const(TagionBill)[] outputs) pure nothrow {
86                     this.outputs = outputs;
87                 }
88             });
89 }
90 
91 Signature[] sign(const(SecureNet[]) nets, const(Contract) contract) {
92     import std.algorithm : map;
93     const message = nets[0].calcHash(contract);
94     return nets
95         .map!(net => net.sign(message))
96         .array;
97 }
98 
99 const(SignedContract) sign(const(SecureNet[]) nets, const(Document[]) inputs, const(Document[]) reads, const(Document) script) {
100     import std.algorithm : map, sort;
101     check(nets.length > 0, "At least one input contract");
102     check(nets.length == inputs.length, "Number of signature does not match the number of inputs");
103     const net = nets[0];
104     SignedContract result;
105     auto sorted_inputs = inputs
106         .map!((input) => cast(DARTIndex) net.dartIndex(input))
107         .enumerate
108         .array
109         .sort!((a, b) => a.value < b.value)
110         .array;
111 
112     result.contract = Contract(
113             sorted_inputs.map!((input) => input.value).array,
114             reads.map!(doc => net.dartIndex(doc)).array,
115             Document(script),
116     );
117     result.signs = sign(sorted_inputs.map!((input) => nets[input.index]).array, result.contract);
118     return result;
119 }
120 
121 bool verify(const(SecureNet) net, const(SignedContract*) signed_contract, const(Pubkey[]) owners) nothrow {
122     import std.algorithm : all;
123     try {
124         if (signed_contract.contract.inputs.length == owners.length) {
125             const message = net.calcHash(signed_contract.contract);
126             return zip(signed_contract.signs, owners)
127                 .all!((a) => net.verify(message, a[0], a[1]));
128         }
129     }
130     catch (Exception e) {
131         // ignore
132     }
133     return false;
134 }
135 
136 bool verify(const(SecureNet) net, const(SignedContract*) signed_contract, const(Document[]) inputs) nothrow {
137     import std.algorithm : map;
138     try {
139         return verify(net, signed_contract, inputs.map!(doc => doc[StdNames.owner].get!Pubkey).array);
140     }
141     catch (Exception e) {
142         //ignore
143     }
144     return false;
145 }
146 
147 @recordType("$@G")
148 struct GenesisEpoch {
149     @label(StdNames.epoch) long epoch_number; //should always be zero
150     Pubkey[] nodes;
151     Document testamony;
152     @label(StdNames.time) sdt_t time;
153     TagionGlobals globals;
154     mixin HiBONRecord!(q{
155         this(const(long) epoch_number, Pubkey[] nodes, const(Document) testamony, const(sdt_t) time, const(TagionGlobals) globals) {
156             this.epoch_number = epoch_number;
157             this.nodes = nodes;
158             this.testamony = testamony;
159             this.time = time;
160             this.globals = globals;
161         }
162     });
163 }
164 
165 @recordType("$@E")
166 struct Epoch {
167     @label(StdNames.epoch) long epoch_number;
168     @label(StdNames.time) sdt_t time; // Time stamp
169     @label(StdNames.bullseye) Fingerprint bullseye;
170     @label(StdNames.previous) Fingerprint previous;
171     @label("$signs") const(Signature)[] signs; /// Signature of all inputs
172     @optional @(filter.Initialized) Pubkey[] active; /// Sorted keys
173     @optional @(filter.Initialized) Pubkey[] deactive;
174     @optional @(filter.Initialized) TagionGlobals globals;
175 
176     mixin HiBONRecord!(q{
177         this(long epoch_number,
178             sdt_t time, 
179             Fingerprint bullseye,
180             Fingerprint previous,
181             const(Signature)[] signs,
182             Pubkey[] active,
183             Pubkey[] deactive,
184             const(TagionGlobals) globals) 
185         {
186             this.epoch_number = epoch_number;
187             this.time = time;
188             this.bullseye = bullseye;
189             this.previous = previous;
190             this.signs = signs;
191             this.active = active;
192             this.deactive = deactive;
193             this.globals = globals;
194         }
195     });
196 }
197 
198 @recordType("$@Tagion")
199 struct TagionHead {
200     @label(StdNames.name) string name; // Default name should always be "tagion"
201     long current_epoch;
202     mixin HiBONRecord!(q{
203         this(const(string) name, const(long) current_epoch) {
204             this.name = name;
205             this.current_epoch = current_epoch;
206         }
207 
208     });
209 }
210 
211 struct TagionGlobals {
212     @label("total") BigNumber total;
213     @label("total_burned") BigNumber total_burned;
214     @label("number_of_bills") long number_of_bills;
215     @label("burnt_bills") long burnt_bills;
216 
217     mixin HiBONRecord!(q{
218         this(const(BigNumber) total, const(BigNumber) total_burned, const(long) number_of_bills, const(long) burnt_bills) {
219             this.total = total;
220             this.total_burned = total_burned;
221             this.number_of_bills = number_of_bills;
222             this.burnt_bills = burnt_bills;
223         }
224     });
225 }
226 
227 @recordType("@$Vote")
228 struct ConsensusVoting {
229     long epoch;
230     @label(StdNames.owner) Pubkey owner;
231     @label(StdNames.signed) Signature signed_bullseye;
232 
233     mixin HiBONRecord!(q{
234         this(long epoch, Pubkey owner, Signature signed_bullseye) pure {
235             this.owner = owner;
236             this.signed_bullseye = signed_bullseye;
237             this.epoch = epoch;
238         }
239         this(const(Document) doc) @safe immutable {
240             immutable _this = ConsensusVoting(doc);
241             this.tupleof = _this.tupleof;
242         }
243     });
244 
245     bool verifyBullseye(const(SecureNet) net, const(Fingerprint) bullseye) const {
246         return net.verify(bullseye, signed_bullseye, owner);
247     }
248 }
249 
250 @recordType("@Locked")
251 struct LockedArchives {
252     @label(StdNames.locked_epoch) long epoch_number;
253     @label("outputs") const(DARTIndex)[] locked_outputs;
254     mixin HiBONRecord!(q{
255         this(long epoch_number, const(DARTIndex)[] locked_outputs) {
256             this.epoch_number = epoch_number;
257             this.locked_outputs = locked_outputs;
258         }
259 
260 
261     });
262 }