-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcoin.js
124 lines (102 loc) · 3.51 KB
/
coin.js
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
"use strict";
const BigInteger = require('jsbn').BigInteger;
const blindSignatures = require('blind-signatures');
const utils = require('./utils.js');
const NUM_COINS_REQUIRED = 10;
const COIN_RIS_LENGTH = 20;
const BANK_STR = "ELECTRONIC_PIGGYBANK";
const IDENT_STR = "IDENT";
// A class representing a single DigiCash-lite coin.
// Note that this coin should not be shared with the bank
// if anonymity is desired.
class Coin {
// Utility function to parse the coin's string format
// and return the left and right identity hashes.
// (A merchant can use these hashes to determine if the client
// sent a valid identity string).
static parseCoin(s) {
let [cnst,amt,guid,leftHashes,rightHashes] = s.split('-');
if (cnst !== BANK_STR) {
throw new Error(`Invalid identity string: ${cnst} received, but ${BANK_STR} expected`);
}
let lh = leftHashes.split(',');
let rh = rightHashes.split(',');
return [lh,rh];
}
// To mint a new coin, you need to specify the purchases,
// the value of the coin, and the public key parameters of
// the bank (given by n and e).
constructor(purchaser, amount, n, e) {
this.amount = amount;
this.n = n;
this.e = e;
this.guid = utils.makeGUID();
this.leftIdent = [];
this.rightIdent = [];
let leftHashes = [];
let rightHashes = [];
for (let i=0; i<COIN_RIS_LENGTH; i++) {
// Making an OTP for the identity string.
let { key, ciphertext } = utils.makeOTP({string: `${IDENT_STR}:${purchaser}`});
this.leftIdent.push(key);
leftHashes.push(utils.hash(key));
this.rightIdent.push(ciphertext);
rightHashes.push(utils.hash(ciphertext));
}
this.coinString = `${BANK_STR}-${this.amount}-${this.guid}-${leftHashes.join(',')}-${rightHashes.join(',')}`;
}
// Calculates the blinded hash of the coin and returns
// the blinding factor needed to unblind the coin.
blind() {
let { blinded, r } = blindSignatures.blind({
message: this.toString(),
N: this.n,
E: this.e,
});
this.blinded = blinded;
return r;
}
// Takes in the blinding factor previously used to blind the coin
// and calculates the unblinded signature from the blinded signature.
unblind(r) {
if (this.signature === undefined) {
throw new Error(`The unblind method is only for unsigned documents.`);
}
this.signature = blindSignatures.unblind({
signed: this.signature,
N: this.n,
r: r,
});
}
// Returns true if the blinding factor and the blinded hash
// match the coin's string representation.
verifyUnblinded(r) {
let n = new BigInteger(this.n);
let e = new BigInteger(this.e);
let blindHash = this.blinded.toString();
let h = blindSignatures.messageToHash(this.toString());
let bigHash = new BigInteger(h, 16);
let b = bigHash.multiply(r.modPow(e, n)).mod(n).toString();
return blindHash === b;
}
// Returns the coin's public string representation.
// The bank should not see this value initially, but otherwise
// it can be safely shared without breaking anonymity.
toString() {
return this.coinString;
}
// For an RIS at position i, returns either the left or right
// half of the identity pair.
getRis(isLeft, i) {
if (isLeft) {
return this.leftIdent[i];
} else {
return this.rightIdent[i];
}
}
}
exports.Coin = Coin;
exports.COIN_RIS_LENGTH = COIN_RIS_LENGTH;
exports.IDENT_STR = IDENT_STR;
exports.BANK_STR = BANK_STR;
exports.NUM_COINS_REQUIRED = NUM_COINS_REQUIRED;