From 44c572c993d1b5560735ae31136b0f25d940e9ce Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Sat, 29 Aug 2026 23:02:01 +0200 Subject: [PATCH] Reject non-canonical Ed25519 signatures --- lib/ed25519/verify_key.rb | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/ed25519/verify_key.rb b/lib/ed25519/verify_key.rb index 7084df0..4bf7574 100644 --- a/lib/ed25519/verify_key.rb +++ b/lib/ed25519/verify_key.rb @@ -3,6 +3,14 @@ module Ed25519 # Public key for verifying digital signatures class VerifyKey + SCALAR_ORDER = [ + 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 + ].pack("C*").freeze + private_constant :SCALAR_ORDER + # Create a Ed25519::VerifyKey from its serialized Twisted Edwards representation # # @param key [String] 32-byte string representing a serialized public key @@ -20,11 +28,11 @@ def initialize(key) # # @return [true] message verified successfully def verify(signature, message) - if signature.length != SIGNATURE_SIZE - raise ArgumentError, "expected #{SIGNATURE_SIZE} byte signature, got #{signature.length}" + if signature.bytesize != SIGNATURE_SIZE + raise ArgumentError, "expected #{SIGNATURE_SIZE} byte signature, got #{signature.bytesize}" end - return true if Ed25519.provider.verify(@key_bytes, signature, message) + return true if canonical_scalar?(signature) && Ed25519.provider.verify(@key_bytes, signature, message) raise VerifyError, "signature verification failed!" end @@ -41,5 +49,17 @@ def to_bytes def inspect "#<#{self.class}:#{@key_bytes.unpack1('H*')}>" end + + private + + def canonical_scalar?(signature) + (KEY_SIZE - 1).downto(0) do |index| + scalar_byte = signature.getbyte(KEY_SIZE + index) + order_byte = SCALAR_ORDER.getbyte(index) + return scalar_byte < order_byte if scalar_byte != order_byte + end + + false + end end end