forked from orlp/ed25519
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ed25519.js
216 lines (194 loc) · 9.91 KB
/
ed25519.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
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
class ED25519 {
static _awaitHandler() {
if (!ED25519._properties.handlerPromise) {
ED25519._properties.handlerPromise = new Promise((resolve, reject) => {
// load the handler
if (typeof(document) !== 'undefined') {
// we are in the browser
var script = document.createElement('script');
script.onload = resolve;
script.onerror = reject;
script.src = ED25519._properties.path + (typeof(WebAssembly)!=='undefined'? 'ed25519-wasm.js' : 'ed25519-asm.js');
document.body.appendChild(script);
} else {
// we are in node
if (typeof(WebAssembly)==='undefined') {
throw Error('Please use a node version with WebAssembly support.');
}
global.ED25519_HANDLER = require(ED25519._properties.path + 'ed25519-wasm.js');
resolve();
}
})
.then(() => new Promise((resolve, reject) => {
ED25519._properties.handler = ED25519_HANDLER({
wasmBinaryFile: ED25519._properties.dependenciesPath + 'ed25519-wasm.wasm',
memoryInitializerPrefixURL: ED25519._properties.dependenciesPath,
onRuntimeInitialized: resolve
});
}))
.then(() => {
const memoryStart = ED25519._properties.handler._get_static_memory_start();
const memorySize = ED25519._properties.handler._get_static_memory_size();
if (memorySize < ED25519.PUBLIC_KEY_SIZE + ED25519.PRIVATE_KEY_SIZE + ED25519.SIGNATURE_SIZE) {
throw Error('Static memory too small');
}
let byteOffset = memoryStart;
ED25519._properties.pubKeyPointer = byteOffset;
ED25519._properties.pubKeyBuffer = new Uint8Array(ED25519._properties.handler.HEAP8.buffer, byteOffset, ED25519.PUBLIC_KEY_SIZE);
byteOffset += ED25519.PUBLIC_KEY_SIZE;
ED25519._properties.privKeyPointer = byteOffset;
ED25519._properties.privKeyBuffer = new Uint8Array(ED25519._properties.handler.HEAP8.buffer, byteOffset, ED25519.PRIVATE_KEY_SIZE);
byteOffset += ED25519.PRIVATE_KEY_SIZE;
ED25519._properties.signaturePointer = byteOffset;
ED25519._properties.signatureBuffer = new Uint8Array(ED25519._properties.handler.HEAP8.buffer, byteOffset, ED25519.SIGNATURE_SIZE);
byteOffset += ED25519.SIGNATURE_SIZE;
ED25519._properties.messagePointer = byteOffset;
ED25519._properties.messageBuffer = new Uint8Array(ED25519._properties.handler.HEAP8.buffer, byteOffset, (memoryStart + memorySize) - byteOffset);
if (ED25519._properties.removePrivKeyTraces) {
// stack to be able to overwrite any traces of private key data on the stack (note that the ed25519
// implementation does not malloc at any time so we don't have to mind that).
// We overwrite the private key data as the HEAP8 with all data is easily accessible for an attacker.
ED25519._properties.stack = ED25519._properties.handler._getStack();
Object.defineProperty(ED25519._properties.stack, 'fill', {
value: Uint8Array.prototype.fill.bind(ED25519._properties.stack)
});
}
// freeze methods (see comment about freeze below)
Object.freeze(ED25519._properties);
[ED25519._properties.pubKeyBuffer, ED25519._properties.privKeyBuffer, ED25519._properties.signatureBuffer,
ED25519._properties.messageBuffer].forEach(buffer => {
Object.defineProperty(buffer, 'fill', {
value: Uint8Array.prototype.fill.bind(buffer)
});
Object.defineProperty(buffer, 'set', {
value: Uint8Array.prototype.set.bind(buffer)
});
});
// deep freeze the handler (the handler and handler.asm might be enough, but just to be sure)
ED25519.deepFreeze(ED25519._properties.handler);
});
}
return ED25519._properties.handlerPromise;
}
static setPath(path, dependenciesPath = path) {
if (ED25519._properties.handlerPromise) {
throw Error('must be set before first call of any method');
}
ED25519._properties.path = path;
ED25519._properties.dependenciesPath = dependenciesPath;
}
static disablePrivateKeyTraceRemoval() {
if (ED25519._properties.handlerPromise) {
throw Error('must be set before first call of any method');
}
ED25519._properties.removePrivKeyTraces = false;
}
/**
* Calculate the public key for a given private key.
* The private and public key are 32 byte. The private key can be any random data, however ensure to
* use random data generated by a cryptographically secure random generator, e.g. window.crypto.getRandomValues.
*/
static async derivePublicKey(out_publicKey, privateKey) {
await ED25519._awaitHandler();
if (out_publicKey.byteLength !== ED25519.PUBLIC_KEY_SIZE
|| privateKey.byteLength !== ED25519.PRIVATE_KEY_SIZE) {
throw Error('Wrong buffer size.');
}
ED25519._properties.privKeyBuffer.set(privateKey);
ED25519._properties.handler._ed25519_public_key_derive(ED25519._properties.pubKeyPointer, ED25519._properties.privKeyPointer);
if (ED25519._properties.removePrivKeyTraces) {
ED25519._properties.privKeyBuffer.fill(0);
ED25519._properties.stack.fill(0);
}
out_publicKey.set(ED25519._properties.pubKeyBuffer);
}
/**
* Creates a signature of the given message with the given key pair. signature must be a writable 64 byte buffer.
* message must have at least message_len bytes to be read and must fit into ED25519._properties.messageBuffer.
*/
static async sign(out_signature, message, publicKey, privateKey) {
await ED25519._awaitHandler();
const messageLength = message.byteLength;
if (out_signature.byteLength !== ED25519.SIGNATURE_SIZE
|| messageLength > ED25519._properties.messageBuffer.byteLength
|| publicKey.byteLength !== ED25519.PUBLIC_KEY_SIZE
|| privateKey.byteLength !== ED25519.PRIVATE_KEY_SIZE) {
throw Error('Wrong buffer size.');
}
ED25519._properties.messageBuffer.set(message);
ED25519._properties.pubKeyBuffer.set(publicKey);
ED25519._properties.privKeyBuffer.set(privateKey);
ED25519._properties.handler._ed25519_sign(ED25519._properties.signaturePointer, ED25519._properties.messagePointer, messageLength,
ED25519._properties.pubKeyPointer, ED25519._properties.privKeyPointer);
if (ED25519._properties.removePrivKeyTraces) {
ED25519._properties.privKeyBuffer.fill(0);
ED25519._properties.stack.fill(0);
}
out_signature.set(ED25519._properties.signatureBuffer);
}
/**
* Verifies the signature on the given message using public_key. signature must be a readable 64 byte buffer.
* message must have at least message_len bytes to be read and must fit ED25519._properties.messageBuffer.
* Returns true if the signature matches, false otherwise.
*/
static async verify(signature, message, publicKey) {
await ED25519._awaitHandler();
const messageLength = message.byteLength;
if (signature.byteLength !== ED25519.SIGNATURE_SIZE
|| message.byteLength > ED25519._properties.messageBuffer.byteLength
|| publicKey.byteLength !== ED25519.PUBLIC_KEY_SIZE) {
throw Error('Wrong buffer size.');
}
ED25519._properties.signatureBuffer.set(signature);
ED25519._properties.messageBuffer.set(message);
ED25519._properties.pubKeyBuffer.set(publicKey);
return !!ED25519._properties.handler._ed25519_verify(ED25519._properties.signaturePointer, ED25519._properties.messagePointer, messageLength,
ED25519._properties.pubKeyPointer);
}
static deepFreeze(obj) {
if (obj._frozen) {
return;
}
try {
Object.freeze(obj);
obj._frozen = true;
} catch(e) {
return;
}
const propNames = Object.getOwnPropertyNames(obj);
propNames.forEach(function(name) {
const prop = obj[name];
if (typeof prop === 'object' && prop !== null) {
ED25519.deepFreeze(prop);
}
});
}
}
ED25519.PUBLIC_KEY_SIZE = 32;
ED25519.PRIVATE_KEY_SIZE = 32;
ED25519.SIGNATURE_SIZE = 64;
ED25519._properties = {
pubKeyBuffer: null,
privKeyBuffer: null,
signatureBuffer: null,
messageBuffer: null,
pubKeyPointer: null,
privKeyPointer: null,
signaturePointer: null,
messagePointer: null,
stack: null,
path: '../node_modules/ed25519/dist/',
dependenciesPath: '../node_modules/ed25519/dist/',
handler: null,
handlerPromise: null,
removePrivKeyTraces: true
};
// Freeze the object to avoid that an attacker can replace the methods and e.g. steal the private key
// or let the signer sign a message he didn't want to sign, e.g. by asking the user to post something
// in the developer tools under false claims or xss. Note however that the attacker can change the
// _properties (including the handlerPromise) before _awaitHandler has finished. So you might want to
// manually call that method at start up.
Object.freeze(ED25519);
if (typeof(module) !== 'undefined') {
module.exports = ED25519;
}