authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2024-06-04 09:11:05+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-06-04 10:11:05+02:00
log332fbb4b027a5529e58b1e46d785718a7faa75e6
treed688489dd015c2e01dbc8d932620460e97dab677
parent993885c04092a53791366edc7f3fe9fbf7c25b7d
signaturebadge-check Signed by PGP key B5690EEEBB952194

crypto.edwards25519: add the ability to check for group membership (#20175)

Most of the functions related to points on the Edwards25519 curve check that input points are not in a small-order subgroup. They don't check that points are on the prime-order subgroup, because this is expensive, and not always necessary. However, applications may require such a check in order to ensure that a public key is valid, and that a secret key counterpart exists. Many functions in the public API of libsodium related to arithmetic over Edwards25519 also do that check unconditionally. This is expensive, but a good way to catch bugs in protocols and implementations. So, add a `rejectUnexpectedSubgroup()` function to achieve this. The documentation on the edwards25519->curve25519 conversion function was also updated, in order to explain how to match libsodium's behavior if necessary. We use an addition chain to multiply the point by the order of the prime group. An alternative we may implement later is Pornin's point halving technique: https://eprint.iacr.org/2022/1164.pdf

3 files changed, 73 insertions(+), 1 deletions(-)

lib/std/crypto/25519/curve25519.zig+8
......@@ -102,6 +102,14 @@ pub const Curve25519 = struct {
102102 }
103103
104104 /// Compute the Curve25519 equivalent to an Edwards25519 point.
105 ///
106 /// Note that the function doesn't check that the input point is
107 /// on the prime order group, e.g. that it is an Ed25519 public key
108 /// for which an Ed25519 secret key exists.
109 ///
110 /// If this is required, for example for compatibility with libsodium's strict
111 /// validation policy, the caller can call the `rejectUnexpectedSubgroup` function
112 /// on the input point before calling this function.
105113 pub fn fromEdwards25519(p: crypto.ecc.Edwards25519) IdentityElementError!Curve25519 {
106114 try p.clearCofactor().rejectIdentity();
107115 const one = crypto.ecc.Edwards25519.Fe.one;
lib/std/crypto/25519/edwards25519.zig+61
......@@ -9,6 +9,7 @@ const IdentityElementError = crypto.errors.IdentityElementError;
99const NonCanonicalError = crypto.errors.NonCanonicalError;
1010const NotSquareError = crypto.errors.NotSquareError;
1111const WeakPublicKeyError = crypto.errors.WeakPublicKeyError;
12const UnexpectedSubgroupError = crypto.errors.UnexpectedSubgroupError;
1213
1314/// Group operations over Edwards25519.
1415pub const Edwards25519 = struct {
......@@ -78,6 +79,46 @@ pub const Edwards25519 = struct {
7879 }
7980 }
8081
82 /// Reject a point if it is not in the prime order subgroup generated by the standard base point.
83 ///
84 /// If the point is not in the main subgroup:
85 ///
86 /// - `WeakPublicKeyError` is returned if the point belongs to a low-order subgroup.
87 /// - `UnexpectedSubgroupError` is returned otherwise.
88 pub fn rejectUnexpectedSubgroup(p: Edwards25519) (WeakPublicKeyError || UnexpectedSubgroupError)!void {
89 try p.rejectLowOrder();
90
91 // Multiply p by the order of subgroup - This is a prime order group, so the result should be the neutral element.
92 const _10 = p.dbl();
93 const _11 = p.add(_10);
94 const _100 = p.add(_11);
95 const _110 = _10.add(_100);
96 const _1000 = _10.add(_110);
97 const _1011 = _11.add(_1000);
98 const _10000 = _1000.dbl();
99 const _100000 = _10000.dbl();
100 const _100110 = _110.add(_100000);
101 const _1000000 = _100000.dbl();
102 const _1010000 = _10000.add(_1000000);
103 const _1010011 = _11.add(_1010000);
104 const _1100011 = _10000.add(_1010011);
105 const _1100111 = _100.add(_1100011);
106 const _1101011 = _100.add(_1100111);
107 const _10010011 = _1000000.add(_1010011);
108 const _10010111 = _100.add(_10010011);
109 const _10111101 = _100110.add(_10010111);
110 const _11010011 = _1000000.add(_10010011);
111 const _11100111 = _1010000.add(_10010111);
112 const _11101101 = _110.add(_11100111);
113 const _11110101 = _1000.add(_11101101);
114 const q = ((_11110101.add(((((_1101011.add(((((_10.add(((_1011.add(_11110101)).shift(126)
115 .add(_1010011)).shift(9).add(_11110101))).shift(7).add(_1100111)).shift(9).add(_11110101).shift(11)
116 .add(_10111101)).shift(8).add(_11100111)).shift(9))).shift(6).add(_1011)).shift(14).add(_10010011).shift(10)
117 .add(_1100011)).shift(9).add(_10010111)).shift(10))).shift(8).add(_11010011)).shift(8).add(_11101101);
118 q.rejectIdentity() catch return;
119 return error.UnexpectedSubgroup;
120 }
121
81122 /// Multiply a point by the cofactor
82123 pub fn clearCofactor(p: Edwards25519) Edwards25519 {
83124 return p.dbl().dbl().dbl();
......@@ -142,6 +183,13 @@ pub const Edwards25519 = struct {
142183 return p.add(q.neg());
143184 }
144185
186 /// Double a point `n` times.
187 fn shift(p: Edwards25519, n: comptime_int) Edwards25519 {
188 var q = p;
189 for (0..n) |_| q = q.dbl();
190 return q;
191 }
192
145193 inline fn cMov(p: *Edwards25519, a: Edwards25519, c: u64) void {
146194 p.x.cMov(a.x, c);
147195 p.y.cMov(a.y, c);
......@@ -575,3 +623,16 @@ test "implicit reduction of invalid scalars" {
575623 try htest.assertEqual("339f189ecc5fbebe9895345c72dc07bda6e615f8a40e768441b6f529cd6c671a", p1.toBytes()[0..]);
576624 try htest.assertEqual("a501e4c595a3686d8bee7058c7e6af7fd237f945c47546910e37e0e79b1bafb0", p3.toBytes()[0..]);
577625}
626
627test "subgroup check" {
628 for (0..100) |_| {
629 var p = Edwards25519.basePoint;
630 const s = Edwards25519.scalar.random();
631 p = try p.mulPublic(s);
632 try p.rejectUnexpectedSubgroup();
633 }
634 var bogus: [Edwards25519.encoded_length]u8 = undefined;
635 _ = try std.fmt.hexToBytes(&bogus, "4dc95e3c28d78c48a60531525e6327e259b7ba0d2f5c81b694052c766a14b625");
636 const p = try Edwards25519.fromBytes(bogus);
637 try std.testing.expectError(error.UnexpectedSubgroup, p.rejectUnexpectedSubgroup());
638}
lib/std/crypto/errors.zig+4-1
......@@ -31,5 +31,8 @@ pub const WeakParametersError = error{WeakParameters};
3131/// Public key would be insecure to use
3232pub const WeakPublicKeyError = error{WeakPublicKey};
3333
34/// Point is not in the prime order group
35pub const UnexpectedSubgroupError = error{UnexpectedSubgroup};
36
3437/// Any error related to cryptography operations
35pub const Error = AuthenticationError || OutputTooLongError || IdentityElementError || EncodingError || SignatureVerificationError || KeyMismatchError || NonCanonicalError || NotSquareError || PasswordVerificationError || WeakParametersError || WeakPublicKeyError;
38pub const Error = AuthenticationError || OutputTooLongError || IdentityElementError || EncodingError || SignatureVerificationError || KeyMismatchError || NonCanonicalError || NotSquareError || PasswordVerificationError || WeakParametersError || WeakPublicKeyError || UnexpectedSubgroupError;