-
Notifications
You must be signed in to change notification settings - Fork 25
/
tlock_test.go
412 lines (324 loc) · 12.7 KB
/
tlock_test.go
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
package tlock_test
import (
"bytes"
_ "embed" // Calls init function.
"errors"
"os"
"path/filepath"
"strings"
"testing"
"time"
chain "github.com/drand/drand/v2/common"
"github.com/drand/drand/v2/crypto"
bls "github.com/drand/kyber-bls12381"
"github.com/drand/tlock"
"github.com/drand/tlock/networks/http"
"github.com/stretchr/testify/require"
)
var (
//go:embed testdata/data.txt
dataFile []byte
//go:embed testdata/lorem.txt
loremBytes []byte
)
const (
testnetHost = "http://pl-us.testnet.drand.sh/"
testnetUnchainedOnG2 = "7672797f548f3f4748ac4bf3352fc6c6b6468c9ad40ad456a397545c6e2df5bf"
testnetQuicknetT = "cc9c398442737cbd141526600919edd69f1d6f9b4adb67e4d912fbc64341a9a5"
mainnetHost = "http://api.drand.sh/"
mainnetQuicknet = "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
)
func TestEarlyDecryptionWithDuration(t *testing.T) {
for host, hashes := range map[string][]string{testnetHost: {testnetUnchainedOnG2, testnetQuicknetT},
mainnetHost: {mainnetQuicknet}} {
for _, hash := range hashes {
network, err := http.NewNetwork(host, hash)
require.NoError(t, err)
// =========================================================================
// Encrypt
// Read the plaintext data to be encrypted.
in, err := os.Open("testdata/data.txt")
require.NoError(t, err)
defer in.Close()
// Write the encoded information to this buffer.
var cipherData bytes.Buffer
// Enough duration to check for a non-existent beacon.
duration := 10 * time.Second
roundNumber := network.RoundNumber(time.Now().Add(duration))
err = tlock.New(network).Encrypt(&cipherData, in, roundNumber)
require.NoError(t, err)
// =========================================================================
// Decrypt
// Write the decoded information to this buffer.
var plainData bytes.Buffer
// We DO NOT wait for the future beacon to exist.
err = tlock.New(network).Decrypt(&plainData, &cipherData)
require.ErrorIs(t, err, tlock.ErrTooEarly)
}
}
}
func TestEarlyDecryptionWithRound(t *testing.T) {
network, err := http.NewNetwork(testnetHost, testnetUnchainedOnG2)
require.NoError(t, err)
// =========================================================================
// Encrypt
// Read the plaintext data to be encrypted.
in, err := os.Open("testdata/data.txt")
require.NoError(t, err)
defer in.Close()
var cipherData bytes.Buffer
futureRound := network.RoundNumber(time.Now().Add(1 * time.Minute))
err = tlock.New(network).Encrypt(&cipherData, in, futureRound)
require.NoError(t, err)
// =========================================================================
// Decrypt
// Write the decoded information to this buffer.
var plainData bytes.Buffer
// We DO NOT wait for the future beacon to exist.
err = tlock.New(network).Decrypt(&plainData, &cipherData)
require.ErrorIs(t, err, tlock.ErrTooEarly)
}
func TestEncryptionWithDuration(t *testing.T) {
if testing.Short() {
t.Skip("skipping live testing in short mode")
}
network, err := http.NewNetwork(testnetHost, testnetUnchainedOnG2)
require.NoError(t, err)
// =========================================================================
// Encrypt
// Read the plaintext data to be encrypted.
in, err := os.Open("testdata/data.txt")
require.NoError(t, err)
defer in.Close()
// Write the encoded information to this buffer.
var cipherData bytes.Buffer
// Enough duration to check for a non-existent beacon.
duration := 4 * time.Second
roundNumber := network.RoundNumber(time.Now().Add(duration))
err = tlock.New(network).Encrypt(&cipherData, in, roundNumber)
require.NoError(t, err)
// =========================================================================
// Decrypt
time.Sleep(5 * time.Second)
// Write the decoded information to this buffer.
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, &cipherData)
require.NoError(t, err)
if !bytes.Equal(plainData.Bytes(), dataFile) {
t.Fatalf("decrypted file is invalid; expected %d; got %d", len(dataFile), len(plainData.Bytes()))
}
}
func TestDecryptVariousChainhashes(t *testing.T) {
dir := "./testdata"
prefix := "lorem-"
files, err := os.ReadDir(dir)
require.NoError(t, err)
network, err := http.NewNetwork(testnetHost, testnetUnchainedOnG2)
require.NoError(t, err)
for _, file := range files {
if strings.HasPrefix(file.Name(), prefix) {
t.Run("Decrypt-"+file.Name(), func(ts *testing.T) {
filePath := filepath.Join(dir, file.Name())
cipherData, err := os.Open(filePath)
require.NoError(ts, err)
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, cipherData)
if errors.Is(err, tlock.ErrWrongChainhash) {
require.Contains(ts, file.Name(), "timevault-mainnet-2024")
return
}
require.NoError(ts, err)
if !bytes.Equal(plainData.Bytes(), loremBytes) {
ts.Fatalf("decrypted file is invalid; expected %d; got %d:\n %v \n %v", len(loremBytes), len(plainData.Bytes()), loremBytes, plainData.Bytes())
}
})
}
}
}
func TestDecryptStrict(t *testing.T) {
dir := "./testdata"
prefix := "lorem-"
files, err := os.ReadDir(dir)
require.NoError(t, err)
network, err := http.NewNetwork(testnetHost, testnetUnchainedOnG2)
require.NoError(t, err)
for _, file := range files {
if strings.Contains(file.Name(), "testnet-unchained-3s-2024") {
continue
}
if strings.Contains(file.Name(), "timevault-testnet-2024") {
continue
}
if strings.HasPrefix(file.Name(), prefix) {
t.Run("DontDecryptStrict-"+file.Name(), func(ts *testing.T) {
filePath := filepath.Join(dir, file.Name())
cipherData, err := os.Open(filePath)
require.NoError(ts, err)
var plainData bytes.Buffer
err = tlock.New(network).Strict().Decrypt(&plainData, cipherData)
require.ErrorIs(ts, err, tlock.ErrWrongChainhash)
})
}
}
}
func TestEncryptionWithRound(t *testing.T) {
if testing.Short() {
t.Skip("skipping live testing in short mode")
}
network, err := http.NewNetwork(testnetHost, testnetUnchainedOnG2)
require.NoError(t, err)
// =========================================================================
// Encrypt
// Read the plaintext data to be encrypted.
in, err := os.Open("testdata/data.txt")
require.NoError(t, err)
defer in.Close()
// Write the encoded information to this buffer.
var cipherData bytes.Buffer
futureRound := network.RoundNumber(time.Now().Add(6 * time.Second))
err = tlock.New(network).Encrypt(&cipherData, in, futureRound)
require.NoError(t, err)
// =========================================================================
// Decrypt
var plainData bytes.Buffer
// Wait for the future beacon to exist.
time.Sleep(10 * time.Second)
err = tlock.New(network).Decrypt(&plainData, &cipherData)
require.NoError(t, err)
if !bytes.Equal(plainData.Bytes(), dataFile) {
t.Fatalf("decrypted file is invalid; expected %d; got %d", len(dataFile), len(plainData.Bytes()))
}
}
func TestTimeLockUnlock(t *testing.T) {
network, err := http.NewNetwork(testnetHost, testnetQuicknetT)
require.NoError(t, err)
futureRound := network.RoundNumber(time.Now())
id, err := network.Signature(futureRound)
require.NoError(t, err)
data := []byte(`anything`)
cipherText, err := tlock.TimeLock(network.Scheme(), network.PublicKey(), futureRound, data)
require.NoError(t, err)
beacon := chain.Beacon{
Round: futureRound,
Signature: id,
}
b, err := tlock.TimeUnlock(network.Scheme(), network.PublicKey(), beacon, cipherText)
require.NoError(t, err)
if !bytes.Equal(data, b) {
t.Fatalf("unexpected bytes; expected len %d; got %d", len(data), len(b))
}
}
func TestCannotEncryptWithPointAtInfinity(t *testing.T) {
suite := bls.NewBLS12381Suite()
t.Run("on G2", func(t *testing.T) {
infinity := suite.G2().Scalar().Zero()
pointAtInfinity := suite.G2().Point().Mul(infinity, nil)
_, err := tlock.TimeLock(*crypto.NewPedersenBLSUnchainedG1(), pointAtInfinity, 10, []byte("deadbeef"))
require.ErrorIs(t, err, tlock.ErrInvalidPublicKey)
})
t.Run("on G1", func(t *testing.T) {
infinity := suite.G1().Scalar().Zero()
pointAtInfinity := suite.G1().Point().Mul(infinity, nil)
_, err := tlock.TimeLock(*crypto.NewPedersenBLSUnchained(), pointAtInfinity, 10, []byte("deadbeef"))
require.ErrorIs(t, err, tlock.ErrInvalidPublicKey)
})
}
func TestDecryptText(t *testing.T) {
cipher := `-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDEyMDQwODgzIDUyZGI5YmE3
MGUwY2MwZjZlYWY3ODAzZGQwNzQ0N2ExZjU0Nzc3MzVmZDNmNjYxNzkyYmE5NDYw
MGM4NGU5NzEKa1JjK01NSEUwS005b1V0SmNLTWZGb1JFVzBXN1JQbTNtdzZpVUJ1
cGNXVkZkZDJQb1h6U0JrK25TM01BNnBKNwpHZDl3REhmVU5hTldXTWw2cGVia2Jh
OUVNZGJDWnBuQVNtOWFIb3hqUitwaGFVT2xoS1ppZGl5ZHBLSStPS2N0CmxvT2ZP
SW9KaGtndTVTRnJUOGVVQTJUOGk3aTBwQlBzTDlTWUJUZEJQb28KLS0tIEl6Q1Js
WSt1RXp0d21CbEg0cTFVZGNJaW9pS2l0M0c0bHVxNlNjT2w3UUUKDI4cDlPHPgjy
UnBmtsw6U2LlKh8iDf0E1PfwDenmKFfQaAGm0WLxdlzP8Q==
-----END AGE ENCRYPTED FILE-----`
t.Run("With valid network", func(tt *testing.T) {
network, err := http.NewNetwork(mainnetHost, mainnetQuicknet)
require.NoError(tt, err)
testReader := strings.NewReader(cipher)
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, testReader)
require.NoError(tt, err)
require.Equal(tt, "hello world", plainData.String())
})
t.Run("With invalid network", func(tt *testing.T) {
network, err := http.NewNetwork(testnetHost, testnetUnchainedOnG2)
require.NoError(tt, err)
testReader := strings.NewReader(cipher)
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, testReader)
require.ErrorIs(tt, err, tlock.ErrWrongChainhash)
})
t.Run("With quicknet-t invalid network", func(tt *testing.T) {
network, err := http.NewNetwork(testnetHost, testnetQuicknetT)
require.NoError(tt, err)
testReader := strings.NewReader(cipher)
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, testReader)
require.ErrorIs(tt, err, tlock.ErrWrongChainhash)
})
}
func TestInteropWithJS(t *testing.T) {
t.Run("on Mainnet with G1 sigs", func(t *testing.T) {
cipher := `-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDEyMDQxMTI1IDUyZGI5YmE3
MGUwY2MwZjZlYWY3ODAzZGQwNzQ0N2ExZjU0Nzc3MzVmZDNmNjYxNzkyYmE5NDYw
MGM4NGU5NzEKbDNtWFdseFRIS0YxQi9HZGYyMzJ0cmkveDFWZk5zVDMwS002eExV
NXUwbFFqQVdNSFJmVHJYbnFJOWpHWWM4ZApETmVodVhaUm8zay9HVzVMVDNaN1M1
d3JVN0lvQVNQUy9xY3JjODNIWEplY25wTXVJS1ZTM3Fyc0NvZzJiZW1OCjVJQmRD
VDU4UUZGeVJ5QzRlRUFZU092NWl0b3E2UWw1RDh6WEtVdmdTTFkKLS0tIEk5c0th
Mi9yeEF2ZDFlL1paTFlIV2VZYkVZVjlreDFidE1wWm1rMU51QkUKxCgEsEjSEixh
4nEBtpolrubLO6WwhfWuh5ZFewjuXbSyrJGreivurDm+7y5stuDO6xPVRpcU+eSQ
RLrz
-----END AGE ENCRYPTED FILE-----`
expected := "hello world and other things"
network, err := http.NewNetwork(mainnetHost, mainnetQuicknet)
require.NoError(t, err)
testReader := strings.NewReader(cipher)
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, testReader)
require.NoError(t, err)
require.Equal(t, expected, plainData.String())
})
t.Run("on Testnet with G2 sigs", func(t *testing.T) {
cipher := `-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDEgNzY3Mjc5N2Y1NDhmM2Y0
NzQ4YWM0YmYzMzUyZmM2YzZiNjQ2OGM5YWQ0MGFkNDU2YTM5NzU0NWM2ZTJkZjVi
ZgpnQUNaY1NzYm55Q0ZneEsrSVB4WFpvcGY5SEZrSG1XUFZRallneWNiZmtKTk1P
VUVUUDM2SU1wNGR1YktNTnBHClJOZkJ5VzZYYlZJVHhtK0tUWnBEa2poVXVxazdl
WDEwRTAxTXB4VkxDancKLS0tIENjeTd4N2VSeUh5Sk54eVFKTGRjQ3ZEQjZTRDA4
ZEFUb0ZyZS9aSHpyWVkKKwNyX6cuEEENAjic1ew7k8G6vyxDrY5NWFbAhkKy0IrN
jLK74v9Latit5qAD7Gu/zTIsQXMuCuUf7ma7
-----END AGE ENCRYPTED FILE-----`
expected := "hello world and other things"
network, err := http.NewNetwork(testnetHost, testnetUnchainedOnG2)
require.NoError(t, err)
testReader := strings.NewReader(cipher)
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, testReader)
require.NoError(t, err)
require.Equal(t, expected, plainData.String())
})
t.Run("on testnet with quicknet-t", func(t *testing.T) {
cipher := `-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDE2MjQ5MTAgY2M5YzM5ODQ0
MjczN2NiZDE0MTUyNjYwMDkxOWVkZDY5ZjFkNmY5YjRhZGI2N2U0ZDkxMmZiYzY0
MzQxYTlhNQpqTTVLOEhWVUFrOFFkNStIL0ZQOHplRkZPSEs4T0pjVG1FNW9LSW1z
bytQRmRDM3lycEdtRGFtck9XMGVycDcxCkVuS1hqL216dmI3RThFMDZMWTNWZEh5
SWh3UFhWWFJlREZ5SHZiTWNPMDdNcWFLamV5MWRNMkMwTHR1SjNpWUoKeENEaEJQ
RDF3K3JjbEtNenI3QU5VVldWa3FmMHd0aGtxTmw3VEEwK0RjQQotLS0gUWFpL0U5
VDNsVkpZT3F2Mk14NWRIU3IzbnhuUUsyaTdsS0ptclNoNk9lOAqkjk0Ypkj6JxKk
5ZxeTXAsxRyy9yptL4yKgd2i/J7k/O3C0Te7yPwsdkUC
-----END AGE ENCRYPTED FILE-----`
expected := "test today\n"
network, err := http.NewNetwork(testnetHost, testnetQuicknetT)
require.NoError(t, err)
testReader := strings.NewReader(cipher)
var plainData bytes.Buffer
err = tlock.New(network).Decrypt(&plainData, testReader)
require.NoError(t, err)
require.Equal(t, expected, plainData.String())
})
}