Skip to content

Conversation

@xinsong-cui
Copy link
Contributor

Issue #

Description of changes

Adds support for generating ECDSA signatures in raw r||s format (64 bytes) in addition to the existing ASN.1 DER format.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

}

public final class aws/smithy/kotlin/runtime/hashing/EcdsaJVMKt {
public static final fun ecdsaSecp256r1 ([B[B)[B
Copy link
Contributor Author

@xinsong-cui xinsong-cui Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing ecdsaSecp256r1(key, message) calls work unchanged, should not be a breaking change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely a breaking change because you've altered the method signature. Older consumers of runtime-core will encounter a NoSuchMethodError exception at runtime. The only way to do this safely without breaking binary compatibility is to maintain the old method signature and add a new one.

I recommend one of the following approaches:

  • Expose a new method ecdsaSecp256r1Rs which invokes ecdsaSecp256r1Rs and extracts the r and s values. This could be implemented in common since it's just byte manipulation
  • Expose a new method asn1DerToRs which converts any ByteArray from ASN.1 DER format to r||s. Callers would then invoke ecdsaSecp256r1Rs first and then asn1DerToRs. This could also be implemented in common.

@xinsong-cui xinsong-cui added no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly. acknowledge-api-break Acknowledge that a change is API breaking and may be backwards-incompatible. Review carefully! labels Nov 4, 2025
Copy link
Contributor

@ianbotsf ianbotsf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: Please add/update tests which verify actual values.

}

public final class aws/smithy/kotlin/runtime/hashing/EcdsaJVMKt {
public static final fun ecdsaSecp256r1 ([B[B)[B
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely a breaking change because you've altered the method signature. Older consumers of runtime-core will encounter a NoSuchMethodError exception at runtime. The only way to do this safely without breaking binary compatibility is to maintain the old method signature and add a new one.

I recommend one of the following approaches:

  • Expose a new method ecdsaSecp256r1Rs which invokes ecdsaSecp256r1Rs and extracts the r and s values. This could be implemented in common since it's just byte manipulation
  • Expose a new method asn1DerToRs which converts any ByteArray from ASN.1 DER format to r||s. Callers would then invoke ecdsaSecp256r1Rs first and then asn1DerToRs. This could also be implemented in common.

Comment on lines 48 to 73
/**
* Parses an ASN.1 DER encoded ECDSA signature and converts it to raw r||s format.
*/
private fun parseDerSignature(derSignature: ByteArray): ByteArray {
var index = 2 // Skip SEQUENCE tag and length

// Read r
index++ // Skip INTEGER tag
val rLength = derSignature[index++].toInt() and 0xFF
val r = derSignature.sliceArray(index until index + rLength)
index += rLength

// Read s
index++ // Skip INTEGER tag
val sLength = derSignature[index++].toInt() and 0xFF
val s = derSignature.sliceArray(index until index + sLength)

// Remove leading zero bytes and pad to 32 bytes
val rFixed = r.dropWhile { it == 0.toByte() }.toByteArray()
val sFixed = s.dropWhile { it == 0.toByte() }.toByteArray()

val rPadded = if (rFixed.size < 32) ByteArray(32 - rFixed.size) + rFixed else rFixed
val sPadded = if (sFixed.size < 32) ByteArray(32 - sFixed.size) + sFixed else sFixed

return rPadded + sPadded
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Where did we find this algorithm? Can we link to a reliable source?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really find any available algorithm, I find this is helpful: the structure of DER (which tells where is r and s located in DER)

@xinsong-cui xinsong-cui removed the acknowledge-api-break Acknowledge that a change is API breaking and may be backwards-incompatible. Review carefully! label Nov 4, 2025
@xinsong-cui xinsong-cui marked this pull request as ready for review November 4, 2025 21:30
@xinsong-cui xinsong-cui requested a review from a team as a code owner November 4, 2025 21:30
Comment on lines +12 to +18
/**
* ECDSA on the SECP256R1 curve returning raw r||s format.
*/
public fun ecdsaSecp256r1Rs(key: ByteArray, message: ByteArray): ByteArray {
val derSignature = ecdsaSecp256r1(key, message)
return parseDerSignature(derSignature)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create some unit tests for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, tests added.
Note: I have to disable signature verification test on JVM8 since SHA256withECDSAinP1363Format is not supported

@xinsong-cui xinsong-cui merged commit 5fc36ff into main Nov 6, 2025
23 checks passed
@xinsong-cui xinsong-cui deleted the ecdsa-raw branch November 6, 2025 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants