Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions core/ed25519_verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,16 @@ static void scalarmult(gf r[4], gf q[4], const uint8_t *s)
}
}

static void scalarbase(gf r[4], const uint8_t *s)
{
gf q[4];
fe_copy16(q[0], BX);
fe_copy16(q[1], BY);
fe_copy16(q[2], gf1);
fe_mul(q[3], BX, BY);
scalarmult(r, q, s);
}

/* The identity encodes as y = 1 with the sign bit clear. Takes a point rather
* than an encoding so both callers below can pass one directly. */
static int point_is_identity(gf p[4])
Expand Down Expand Up @@ -311,13 +321,9 @@ static void scalarbase(gf r[4], const uint8_t *s)
* for every point whose order divides L -- including the identity itself,
* whose order is 1 -- so the identity must also be excluded explicitly.
*
* The scalar is derived from ORDER_L rather than written out a second time,
* so there is no separate constant to transcribe wrongly: a mistyped L would
* reject valid keys, and only in the field.
*
* The key arrives negated from unpackneg(). [L](-A) = -[L]A and the identity
* is its own negation, so neither condition is affected by the sign.
*/
* Public keys must be non-identity points in Ed25519's prime-order subgroup.
* Merely decoding a point is insufficient: an identity or torsion key can
* make the verification equation true without knowledge of a private key. */
static int public_key_is_valid_subgroup(gf public_key[4])
{
uint8_t order_l[32];
Expand Down
19 changes: 11 additions & 8 deletions include/eos_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,21 @@ EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, flags) == 24,
"flags must stay at offset 24");
EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, sig_len) == 61,
"sig_len must stay at offset 61");
/* tlv_len and tlv_hash are asserted above, where #93 introduced them; the
* 30 bytes they occupy are the ones this block used to pin as reserved[]. */
/* offsetof(tlv_len) == 62 and offsetof(tlv_hash) == 64 are already asserted
* above, next to EOS_IMG_TLV_HASH_LEN -- this used to be a single reserved[30]
* field before #93 split it into tlv_len + tlv_hash for anti-rollback, and
* this block was never updated to match, leaving `reserved` referring to a
* struct member that no longer exists (a hard compile error, not just a stale
* comment: eos_image_header_t has never had every field pinned since #93).
*/

/* Field widths. An offset assert cannot see a field growing into padding that
* happens to keep every later offset -- the 30 bytes at 62 absorb exactly
* that, which is why both halves of that span carry a width assert. */
* happens to keep every later offset -- tlv_hash[] absorbs exactly that. */
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->hash) == 32,
"hash[] is 32 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_len) == 2,
"tlv_len is 2 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_hash) == 28,
"tlv_hash is 28 bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->tlv_hash) ==
EOS_IMG_TLV_HASH_LEN,
"tlv_hash[] is EOS_IMG_TLV_HASH_LEN bytes on the wire");
EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->signature) == 64,
"signature[] is 64 bytes on the wire");

Expand Down
44 changes: 44 additions & 0 deletions tests/unit/test_ed25519.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,46 @@ static const struct rfc_vector k_vectors[] = {

#define N_VECTORS (sizeof(k_vectors) / sizeof(k_vectors[0]))

/* All eight compressed encodings of edwards25519's order-8 torsion subgroup
* (the cyclic group <G> for any order-8 point G, i.e. {1G, 2G, ..., 8G=O}).
* Order 8/gcd(k,8) for kG: orders present are 1 (identity), 2, 4, 4, 8, 8, 8, 8.
*
* Not hand-transcribed: derived by decoding an order-8 generator from this
* file's own unpackneg()/point_add()/point_is_identity(), then enumerating
* 1G..8G with point_add() and re-encoding with point_pack() -- so their
* correctness rests on the same curve arithmetic this file already uses for
* real verification, not on a separately-copied constant that could carry a
* transcription error. Cross-checked: point k and point (8-k) differ only in
* the sign bit (byte 31), as required since (8-k)G = -(kG); the order-2
* element (k=4) is its own negation, as required since 2P = O implies P = -P.
*/
static const uint8_t k_low_order[8][32] = {
{0x26,0xe8,0x95,0x8f,0xc2,0xb2,0x27,0xb0,0x45,0xc3,0xf4,0x89,0xf2,0xef,0x98,0xf0,
0xd5,0xdf,0xac,0x05,0xd3,0xc6,0x33,0x39,0xb1,0x38,0x02,0x88,0x6d,0x53,0xfc,0x85},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80},
{0xc7,0x17,0x6a,0x70,0x3d,0x4d,0xd8,0x4f,0xba,0x3c,0x0b,0x76,0x0d,0x10,0x67,0x0f,
0x2a,0x20,0x53,0xfa,0x2c,0x39,0xcc,0xc6,0x4e,0xc7,0xfd,0x77,0x92,0xac,0x03,0xfa},
{0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
{0xc7,0x17,0x6a,0x70,0x3d,0x4d,0xd8,0x4f,0xba,0x3c,0x0b,0x76,0x0d,0x10,0x67,0x0f,
0x2a,0x20,0x53,0xfa,0x2c,0x39,0xcc,0xc6,0x4e,0xc7,0xfd,0x77,0x92,0xac,0x03,0x7a},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x26,0xe8,0x95,0x8f,0xc2,0xb2,0x27,0xb0,0x45,0xc3,0xf4,0x89,0xf2,0xef,0x98,0xf0,
0xd5,0xdf,0xac,0x05,0xd3,0xc6,0x33,0x39,0xb1,0x38,0x02,0x88,0x6d,0x53,0xfc,0x05},
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
};

/* A handful of distinct messages, so a low-order-R rejection isn't pinned
* against only one k = SHA-512(R || A || M) value. */
static const char *messages[] = {
"",
"a",
"untrusted firmware",
};

/* ---- positive tests: a conforming signature MUST be accepted ---- */

TEST(test_ed25519_rfc8032_vectors_accepted)
Expand Down Expand Up @@ -337,6 +377,8 @@ TEST(test_ed25519_zero_signature_rejected)
ASSERT(eos_ed25519_verify(sig, pk, msg, 1) != EOS_OK);
}

/* ---- SHA-512, the hash Ed25519 is defined over (FIPS 180-4) ---- */

TEST(test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery)
{
/* The subgroup check guards the public key, not R, and that is
Expand Down Expand Up @@ -433,9 +475,11 @@ int main(void)
run_test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery();
run_test_ed25519_zero_pubkey_rejected();
run_test_ed25519_zero_signature_rejected();
run_test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery();
run_test_sha512_known_answers();
run_test_sha512_streaming_matches_one_shot();

tests_run = 13;
printf("\n%d/%d tests passed\n", tests_passed, tests_run);
return (tests_passed == tests_run) ? 0 : 1;
}