Skip to content

Commit f719d16

Browse files
committed
feat: Implement Ed25519 to Curve25519
See https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519
1 parent 00031d4 commit f719d16

File tree

5 files changed

+332
-2
lines changed

5 files changed

+332
-2
lines changed

packages/sodium/lib/src/api/sumo/sign_sumo.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,14 @@ abstract class SignSumo implements Sign {
2020
///
2121
/// See https://libsodium.gitbook.io/doc/public-key_cryptography/public-key_signatures#extracting-the-seed-and-the-public-key-from-the-secret-key
2222
Uint8List skToPk(SecureKey secretKey);
23+
24+
/// Provides crypto_sign_ed25519_pk_to_curve25519.
25+
///
26+
/// See https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519
27+
Uint8List pkToCurve25519(Uint8List publicKey);
28+
29+
/// Provides crypto_sign_ed25519_sk_to_curve25519.
30+
///
31+
/// See https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519
32+
Uint8List skToCurve25519(SecureKey secretKey);
2333
}

packages/sodium/lib/src/ffi/api/sumo/sign_sumo_ffi.dart

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:meta/meta.dart';
66
import '../../../api/secure_key.dart';
77
import '../../../api/sodium_exception.dart';
88
import '../../../api/sumo/sign_sumo.dart';
9+
import '../../bindings/memory_protection.dart';
910
import '../../bindings/secure_key_native.dart';
1011
import '../../bindings/sodium_pointer.dart';
1112
import '../secure_key_ffi.dart';
@@ -46,8 +47,7 @@ class SignSumoFFI extends SignFFI implements SignSumo {
4647
Uint8List skToPk(SecureKey secretKey) {
4748
validateSecretKey(secretKey);
4849

49-
final publicKey =
50-
SodiumPointer<UnsignedChar>.alloc(sodium, count: publicKeyBytes);
50+
final publicKey = _allocatePublicKey();
5151
try {
5252
final result = secretKey.runUnlockedNative(
5353
sodium,
@@ -63,4 +63,52 @@ class SignSumoFFI extends SignFFI implements SignSumo {
6363
publicKey.dispose();
6464
}
6565
}
66+
67+
@override
68+
Uint8List pkToCurve25519(Uint8List publicKey) {
69+
validatePublicKey(publicKey);
70+
71+
final ed25519PublicKey = publicKey.toSodiumPointer<UnsignedChar>(
72+
sodium,
73+
memoryProtection: MemoryProtection.readOnly,
74+
);
75+
final curve25519PublicKey = _allocatePublicKey();
76+
77+
try {
78+
final result = sodium.crypto_sign_ed25519_pk_to_curve25519(
79+
curve25519PublicKey.ptr,
80+
ed25519PublicKey.ptr,
81+
);
82+
SodiumException.checkSucceededInt(result);
83+
84+
return Uint8List.fromList(curve25519PublicKey.asListView());
85+
} finally {
86+
ed25519PublicKey.dispose();
87+
curve25519PublicKey.dispose();
88+
}
89+
}
90+
91+
@override
92+
Uint8List skToCurve25519(SecureKey secretKey) {
93+
validateSecretKey(secretKey);
94+
95+
final publicKey = _allocatePublicKey();
96+
try {
97+
final result = secretKey.runUnlockedNative(
98+
sodium,
99+
(secretKeyPtr) => sodium.crypto_sign_ed25519_sk_to_curve25519(
100+
publicKey.ptr,
101+
secretKeyPtr.ptr,
102+
),
103+
);
104+
SodiumException.checkSucceededInt(result);
105+
106+
return Uint8List.fromList(publicKey.asListView());
107+
} finally {
108+
publicKey.dispose();
109+
}
110+
}
111+
112+
SodiumPointer<UnsignedChar> _allocatePublicKey() =>
113+
SodiumPointer<UnsignedChar>.alloc(sodium, count: publicKeyBytes);
66114
}

packages/sodium/lib/src/js/api/sumo/sign_sumo_js.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,24 @@ class SignSumoJS extends SignJS implements SignSumo {
3838
),
3939
);
4040
}
41+
42+
@override
43+
Uint8List pkToCurve25519(Uint8List publicKey) {
44+
validatePublicKey(publicKey);
45+
46+
return jsErrorWrap(
47+
() => sodium.crypto_sign_ed25519_pk_to_curve25519(publicKey),
48+
);
49+
}
50+
51+
@override
52+
Uint8List skToCurve25519(SecureKey secretKey) {
53+
validateSecretKey(secretKey);
54+
55+
return jsErrorWrap(
56+
() => secretKey.runUnlockedSync(
57+
sodium.crypto_sign_ed25519_sk_to_curve25519,
58+
),
59+
);
60+
}
4161
}

packages/sodium/test/unit/ffi/api/sumo/sign_sumo_ffi_test.dart

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
library sign_sumo_ffi_test;
55

66
import 'dart:ffi';
7+
import 'dart:typed_data';
78

89
import 'package:mocktail/mocktail.dart';
910
import 'package:sodium/src/api/sodium_exception.dart';
@@ -182,5 +183,156 @@ void main() {
182183
verify(() => mockSodium.sodium_free(any())).called(2);
183184
});
184185
});
186+
187+
group('pkToCurve25519', () {
188+
test('asserts if publicKey is invalid', () {
189+
expect(
190+
() => sut.pkToCurve25519(Uint8List.fromList(const [])),
191+
throwsA(isA<RangeError>()),
192+
);
193+
194+
verify(() => mockSodium.crypto_sign_publickeybytes());
195+
});
196+
197+
test('calls crypto_sign_ed25519_pk_to_curve25519 with correct arguments',
198+
() {
199+
when(
200+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(
201+
any(),
202+
any(),
203+
),
204+
).thenReturn(0);
205+
206+
final publicKey = Uint8List.fromList(
207+
List.generate(5, (index) => 30 + index),
208+
);
209+
210+
sut.pkToCurve25519(publicKey);
211+
212+
verifyInOrder([
213+
() => mockSodium.sodium_allocarray(5, 1),
214+
() => mockSodium.sodium_mprotect_readonly(
215+
any(that: hasRawData(publicKey)),
216+
),
217+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(
218+
any(that: isNot(nullptr)),
219+
any(that: hasRawData<UnsignedChar>(publicKey)),
220+
),
221+
]);
222+
});
223+
224+
test('returns the curve25519 public key of the ed25519 public key', () {
225+
final curve25519PublicKey = Uint8List.fromList(
226+
List.generate(5, (index) => 30 + index),
227+
);
228+
when(
229+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(
230+
any(),
231+
any(),
232+
),
233+
).thenAnswer((i) {
234+
fillPointer(
235+
i.positionalArguments.first as Pointer,
236+
curve25519PublicKey,
237+
);
238+
return 0;
239+
});
240+
241+
final result = sut.pkToCurve25519(curve25519PublicKey);
242+
243+
expect(result, curve25519PublicKey);
244+
245+
verify(() => mockSodium.sodium_free(any())).called(2);
246+
});
247+
248+
test('throws exception on failure', () {
249+
when(
250+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(
251+
any(),
252+
any(),
253+
),
254+
).thenReturn(1);
255+
256+
expect(
257+
() => sut.pkToCurve25519(
258+
Uint8List.fromList(List.generate(5, (index) => 30 + index)),
259+
),
260+
throwsA(isA<SodiumException>()),
261+
);
262+
263+
verify(() => mockSodium.sodium_free(any())).called(2);
264+
});
265+
});
266+
267+
group('skToCurve25519', () {
268+
test('asserts if secretKey is invalid', () {
269+
expect(
270+
() => sut.skToCurve25519(SecureKeyFake.empty(10)),
271+
throwsA(isA<RangeError>()),
272+
);
273+
274+
verify(() => mockSodium.crypto_sign_secretkeybytes());
275+
});
276+
277+
test('calls crypto_sign_ed25519_sk_to_curve25519 with correct arguments',
278+
() {
279+
when(
280+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(
281+
any(),
282+
any(),
283+
),
284+
).thenReturn(0);
285+
286+
final secretKey = List.generate(5, (index) => 30 + index);
287+
288+
sut.skToCurve25519(SecureKeyFake(secretKey));
289+
290+
verifyInOrder([
291+
() => mockSodium.sodium_allocarray(5, 1),
292+
() => mockSodium.sodium_mprotect_readonly(
293+
any(that: hasRawData(secretKey)),
294+
),
295+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(
296+
any(that: isNot(nullptr)),
297+
any(that: hasRawData<UnsignedChar>(secretKey)),
298+
),
299+
]);
300+
});
301+
302+
test('returns the public key of the secret key', () {
303+
final publicKey = List.generate(5, (index) => 100 - index);
304+
when(
305+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(
306+
any(),
307+
any(),
308+
),
309+
).thenAnswer((i) {
310+
fillPointer(i.positionalArguments.first as Pointer, publicKey);
311+
return 0;
312+
});
313+
314+
final result = sut.skToCurve25519(SecureKeyFake.empty(5));
315+
316+
expect(result, publicKey);
317+
318+
verify(() => mockSodium.sodium_free(any())).called(2);
319+
});
320+
321+
test('throws exception on failure', () {
322+
when(
323+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(
324+
any(),
325+
any(),
326+
),
327+
).thenReturn(1);
328+
329+
expect(
330+
() => sut.skToCurve25519(SecureKeyFake.empty(5)),
331+
throwsA(isA<SodiumException>()),
332+
);
333+
334+
verify(() => mockSodium.sodium_free(any())).called(2);
335+
});
336+
});
185337
});
186338
}

packages/sodium/test/unit/js/api/sumo/sign_sumo_js_test.dart

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,105 @@ void main() {
133133
);
134134
});
135135
});
136+
137+
group('pkToCurve25519', () {
138+
test('asserts if publicKey is invalid', () {
139+
expect(
140+
() => sut.pkToCurve25519(Uint8List(0)),
141+
throwsA(isA<RangeError>()),
142+
);
143+
144+
verify(() => mockSodium.crypto_sign_PUBLICKEYBYTES);
145+
});
146+
147+
test('calls crypto_sign_ed25519_pk_to_curve25519 with correct arguments',
148+
() {
149+
when(
150+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(any()),
151+
).thenReturn(Uint8List(0));
152+
153+
final publicKey = List.generate(5, (index) => 30 + index);
154+
155+
sut.pkToCurve25519(Uint8List.fromList(publicKey));
156+
157+
verify(
158+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(
159+
Uint8List.fromList(publicKey),
160+
),
161+
);
162+
});
163+
164+
test('returns the curve25519 public key of the ed25519 public key', () {
165+
final curve25519PublicKey = List.generate(5, (index) => 100 - index);
166+
when(
167+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(any()),
168+
).thenReturn(Uint8List.fromList(curve25519PublicKey));
169+
170+
final result = sut.pkToCurve25519(Uint8List(5));
171+
172+
expect(result, curve25519PublicKey);
173+
});
174+
175+
test('throws exception on failure', () {
176+
when(
177+
() => mockSodium.crypto_sign_ed25519_pk_to_curve25519(any()),
178+
).thenThrow(JsError());
179+
180+
expect(
181+
() => sut.pkToCurve25519(Uint8List(5)),
182+
throwsA(isA<SodiumException>()),
183+
);
184+
});
185+
});
186+
187+
group('skToCurve25519', () {
188+
test('asserts if secretKey is invalid', () {
189+
expect(
190+
() => sut.skToCurve25519(SecureKeyFake.empty(10)),
191+
throwsA(isA<RangeError>()),
192+
);
193+
194+
verify(() => mockSodium.crypto_sign_SECRETKEYBYTES);
195+
});
196+
197+
test('calls crypto_sign_ed25519_sk_to_curve25519 with correct arguments',
198+
() {
199+
when(
200+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(any()),
201+
).thenReturn(Uint8List(0));
202+
203+
final secretKey = List.generate(5, (index) => 30 + index);
204+
205+
sut.skToCurve25519(SecureKeyFake(secretKey));
206+
207+
verify(
208+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(
209+
Uint8List.fromList(secretKey),
210+
),
211+
);
212+
});
213+
214+
test('returns the curve25519 secret key of the ed25519 secret key', () {
215+
final curve25519SecretKey = List.generate(5, (index) => 100 - index);
216+
when(
217+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(any()),
218+
).thenReturn(Uint8List.fromList(curve25519SecretKey));
219+
220+
final result = sut.skToCurve25519(SecureKeyFake.empty(5));
221+
222+
expect(result, curve25519SecretKey);
223+
});
224+
225+
test('throws exception on failure', () {
226+
when(
227+
() => mockSodium.crypto_sign_ed25519_sk_to_curve25519(any()),
228+
).thenThrow(JsError());
229+
230+
expect(
231+
() => sut.skToCurve25519(SecureKeyFake.empty(5)),
232+
throwsA(isA<SodiumException>()),
233+
);
234+
});
235+
});
136236
});
137237
}

0 commit comments

Comments
 (0)