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 }