-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathescrow.0.jsx.exp
297 lines (234 loc) · 10.1 KB
/
escrow.0.jsx.exp
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
/* Lexon-generated Javascript
code: Escrow
file: lexon/escrow.0.lex
compiler: lexon 0.3 alpha 93 U
grammar: 0.2.20 / subset 0.3.8 alpha 79 - English / Reyes
backend: javascript 0.3.93 U
target: node 14.1+
parameters: --javascript --all-auxiliaries
INSTRUCTIONS FOR USE:
Execute this program using node. Replace the <parameters> with literal values.
Running this program as-is requires beginners programmer knowledge. This phase is
yet not covered by lexon's mission to make code readable and useful for non-coders.
In the future, an interface will be generated to complete this last mile. However,
embedding this code into a self-explanatory user interface is a straight forward
task for a full-stack programmer.
Note that the instructions below reflect your lexon code as well as the parameters
used during compilation of the code: different functions and parameters will result
from different input. Some functions are 'built-in' but only appear when needed as
per compiled-in features – a list of which is available with lexon -h. The functions
are not given in a specific order of execution but as listed in the lexon source.
These node modules have to be installed once:
$ npm install serialize-javascript
$ npm install tar
$ npm install nodemailer
$ npm install prompt-sync
Parameters below are marked with double angle brackets << >> for the respective
required caller. If the role is defined earlier, it can only be performed by this
person. (But remember that this entire setup is trustful: anyone can manipulate
anything about this contract. Though they cannot sign it or change the signed log.)
If the role is not defined earlier, the call makes the role be assigned to the
person named for the call. Some functions can be called without naming a caller.
Some clauses of the original lexon source will not appear below. Namely, those
that have no permission phrase, wherefore they are regarded as internal.
The main contract system is initialized by loading the module and instantiating:
$ node
> contract = require("./lexon/escrow.0.jsx");
> escrow = new contract(<<payer>>, <amount>, <payee>, <arbiter>, <fee>);
Remember to reset node's module cache each time you edit and recompile your code:
> delete require.cache[require.resolve('./lexon/escrow.0.jsx')];
These are the state progress functions that allow to interact with the contract:
> escrow.pay_out(<<arbiter>>)
> escrow.pay_back(<<arbiter>>)
state changes of the contract can be listed, e.g. actions performed by
a party to it, or agents who are assigned privileges. In case hash chains
or signatures are used, they are visible in this log. The log is stored in
in the file 'log'.
> escrow.history()
The complete contract state can be saved to disk and re-loaded at a
later point in time. This serves to continue work after stopping and
restarting node; or to send the entire contract system and its current
state - which can include hashes and signatures - to another party,
who may perform the next steps.
> escrow.persist()
> escrow.load()
The contract code, state and log can be bundled into one file to exchange
or archive it:
> escrow.bundle()
> escrow.unbundle()
The contract code, state and log can be sent to a counterparty. This
requires configuring an email account in the file 'config'.
> escrow.send()
Keys for signing log entries are expected on-file, by default named after
the actor, with the extension .key. For demo purposes, key files can be
created using this utility function:
> escrow.create_key(name, passphrase)
*/
var fs = require('fs');
var crypto = require('crypto');
var serialize = require('serialize-javascript');
var prompt = require('prompt-sync')();
var tar = require('tar');
var nodemailer = require('nodemailer');
var last_caller;
var last_passphrase;
/**
**
** Main Escrow contract system
**
**/
/** LEX Escrow.
*
* "Payer" is a person.
* "Payee" is a person.
* "Arbiter" is a person.
* "Amount" is an amount.
* "Fee" is an amount.
*
* The Payer pays an Amount into escrow, appoints the Payee, appoints the Arbiter, and fixes the Fee.
**/
module.exports = class Escrow {
constructor(payer, amount, payee, arbiter, fee) {
let main = this;
/* object members: skip for restoring serialized object */
if(typeof payer !== 'undefined') {
this._escrow = 0;
this.payer = payer;
this.payee = payee;
this.arbiter = arbiter;
this.amount = amount;
this.fee = fee;
this.logname = 'log';
/* start log - overwrites previous by same name */
fs.writeFileSync(this.logname, "Lexon log " + (new Date).toLocaleString('en-US') + "\n", ()=>{});
this._pay(caller, this.payer, 'escrow', this.amount);
this.log(payer, "✓ Payee appointed");
this.log(payer, "✓ Arbiter appointed");
this.log(payer, "✓ Fee fixed");
}
/* restore object from file - must be below class definition */
if(typeof payer === 'undefined') {
console.log("> restore from file 'state'");
var data = fs.readFileSync('state', ()=>{});
var live = eval('(' + data + ')');
Object.assign(this, live);
}
}
/* Pay Out clause */
/*
* CLAUSE: Pay Out.
* The Arbiter may pay from escrow the Fee to themselves, and afterwards pay the remainder of the escrow to the Payee.
*/
pay_out(caller) {
if(caller == this.arbiter) {
this._pay(caller, 'escrow', this.arbiter, this.fee);
this._pay(caller, 'escrow', this.payee, this._escrow);
} else {
return 'not permitted.';
}
return 'done.';
}
/* Pay Back clause */
/*
* CLAUSE: Pay Back.
* The Arbiter may pay from escrow the Fee to themselves, and afterwards return the remainder of the escrow to the Payer.
*/
pay_back(caller) {
if(caller == this.arbiter) {
this._pay(caller, 'escrow', this.arbiter, this.fee);
this._pay(caller, 'escrow', this.payer, this._escrow);
} else {
return 'not permitted.';
}
return 'done.';
}
/* built-in convenience function to view state change log. */
history() {
fs.readFile(this.logname, (e,d)=>{console.log(d.toString())});
}
/* built-in serialization and storage of entire contract system state. */
persist() {
console.log('> persisting');
var data = serialize(this, {space: 4});
fs.writeFileSync('state', data, ()=>{});
}
/* re-instate entire contract system from serialized file store */
static load() {
return new Escrow();
}
/* built-in tar-balling of code, log and state. */
bundle() {
console.log('> bundling into contract.tgz');
tar.create({gzip:true, file:'contract.tgz'}, ['lexon/escrow.0.lex', 'lexon/escrow.0.jsx', 'state', 'log', 'INSTRUCTIONS.TXT']);
}
/* built-in untar-balling of code, log and state. */
static unbundle() {
console.log('> unbundling contract.tgz');
tar.extract('contract.tgz');
}
/* built-in email sending of code, log and state. */
send() {
this.persist();
this.bundle();
console.log('> sending via email');
var receiver = prompt('enter receiver address: ');
var config = fs.readFileSync('config', ()=>{});
var email = eval('(' + config + ')').email;
console.log(email);
var transporter = nodemailer.createTransport({
service: email.service,
auth: { user: email.user, pass: email.pass }});
var mailOptions = {
from: email.from,
to: receiver,
subject: email.subject,
text: email.text,
attachments: { path: './contract.tgz', contentType: 'application/gzip' }};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
} else {
console.log('> email sent: ' + info.response); }});
}
/* built-in logging of state changes. */
log(caller, msg) {
console.log(msg);
let stamp = (new Date()).toLocaleString('en-US');
var entry = `⌽ ${stamp} ✦ ${caller} ${msg}`;
var passphrase = this.sync_passphrase(caller);
var pem = fs.readFileSync(caller + '.key');
var key = pem.toString('ascii');
var sign = crypto.createSign('RSA-SHA256');
sign.update(entry);
var sig = sign.sign({ key: key, passphrase: passphrase }, 'hex');
fs.appendFileSync(this.logname, `${entry} ❈ ${sig}\n`);
let pay = fs.readFileSync(this.logname);
let hash = crypto.createHash('sha256').update(pay);
fs.appendFileSync(this.logname, '⧉ ' + hash.digest('hex').substr(0, 12) + " ");
}
/* built-in password query for private key file, with cache. */
sync_passphrase(caller) {
if(!caller) process.exit('no caller information');
if(caller == last_caller) return last_passphrase;
last_caller = caller;
return last_passphrase = prompt('enter pass phrase for ' + caller + ': ', {echo: ''});
}
/* built-in convenience function to create keys for users. */
static create_key(name, passphrase) {
const { publicKey, privateKey } =
crypto.generateKeyPairSync('rsa',
{ modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: passphrase }});
fs.writeFileSync(name+'.key', privateKey);
fs.writeFileSync(name+'.pub', publicKey);
return true;
}
/* built-in pay message */
_pay(caller, from, to, amount) {
this.log(caller, `➠ system message: pay ${amount} from ${from} to ${to}.`);
if(from == 'escrow') main._escrow -= amount;
if(to == 'escrow') main._escrow += amount;
}
}
/* end */