generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add sign and verify test vectors (#292)
* add sign and verify test vectors * test vectors to kotlin * rename * updates * update * updates * rename
- Loading branch information
1 parent
6944830
commit 7042127
Showing
6 changed files
with
383 additions
and
1 deletion.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
bound/kt/src/main/kotlin/web5/sdk/crypto/verifiers/Ed25519Verifier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package web5.sdk.crypto.verifiers | ||
|
||
import web5.sdk.crypto.keys.Jwk | ||
import web5.sdk.rust.Ed25519Verifier as RustCoreEd25519Verifier | ||
|
||
class Ed25519Verifier : Verifier { | ||
private val rustCoreVerifier: RustCoreEd25519Verifier | ||
|
||
constructor(privateKey: Jwk) { | ||
this.rustCoreVerifier = RustCoreEd25519Verifier(privateKey.rustCoreJwkData) | ||
} | ||
|
||
private constructor(rustCoreVerifier: RustCoreEd25519Verifier) { | ||
this.rustCoreVerifier = rustCoreVerifier | ||
} | ||
|
||
/** | ||
* Implementation of Signer's verify instance method for Ed25519. | ||
* | ||
* @param message the data to be verified. | ||
* @param signature the signature to be verified. | ||
* @return ByteArray the signature. | ||
*/ | ||
override fun verify(message: ByteArray, signature: ByteArray): Boolean { | ||
return rustCoreVerifier.verify(message, signature); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
bound/kt/src/main/kotlin/web5/sdk/crypto/verifiers/Verifier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package web5.sdk.crypto.verifiers | ||
|
||
import web5.sdk.rust.Verifier as RustCoreVerifier | ||
|
||
typealias Verifier = RustCoreVerifier |
212 changes: 212 additions & 0 deletions
212
bound/kt/src/test/kotlin/web5/sdk/Web5TestVectorsTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
package web5.sdk | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude | ||
import com.fasterxml.jackson.core.type.TypeReference | ||
import com.fasterxml.jackson.databind.DeserializationFeature | ||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.fasterxml.jackson.databind.PropertyNamingStrategy | ||
import com.fasterxml.jackson.databind.SerializationFeature | ||
import com.fasterxml.jackson.databind.cfg.MapperConfig | ||
import com.fasterxml.jackson.databind.introspect.AnnotatedField | ||
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod | ||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper | ||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule | ||
import org.junit.jupiter.api.Test | ||
import java.io.File | ||
import org.junit.jupiter.api.Assertions.* | ||
import org.junit.jupiter.api.Nested | ||
import web5.sdk.crypto.keys.Jwk | ||
import web5.sdk.crypto.signers.Ed25519Signer | ||
import web5.sdk.crypto.verifiers.Ed25519Verifier | ||
import web5.sdk.dids.Document | ||
import web5.sdk.rust.DocumentMetadataData | ||
import web5.sdk.rust.ResolutionMetadataData | ||
|
||
class Web5TestVectorsTest { | ||
|
||
class TestVectors<I, O>( | ||
val description: String, | ||
val vectors: List<TestVector<I, O>> | ||
) | ||
|
||
class TestVector<I, O>( | ||
val description: String, | ||
val input: I, | ||
val output: O?, | ||
val errors: Boolean? = false, | ||
) | ||
|
||
data class SignTestInput( | ||
val data: String, | ||
val key: TestVectorJwk, | ||
) | ||
|
||
data class TestVectorJwk( | ||
val crv: String, | ||
val d: String?, | ||
val kid: String, | ||
val kty: String, | ||
val x: String | ||
) | ||
|
||
data class VerifyTestInput( | ||
val key: Map<String, Any>, | ||
val signature: String, | ||
val data: String | ||
) | ||
|
||
data class DidJwkResolveTestOutput( | ||
val context: String?, | ||
val didDocument: Document?, | ||
val didDocumentMetadata: DocumentMetadataData, | ||
val didResolutionMetadata: ResolutionMetadataData? | ||
) | ||
|
||
@Nested | ||
inner class Web5TestVectorsCryptoEd25519 { | ||
@Test | ||
fun sign() { | ||
val typeRef = object : TypeReference<TestVectors<SignTestInput, String>>() {} | ||
val testVectors = | ||
Json.jsonMapper.readValue(File("../../web5-spec/test-vectors/crypto_ed25519/sign.json"), typeRef) | ||
|
||
testVectors.vectors.forEach { vector -> | ||
val inputByteArray = hexStringToByteArray(vector.input.data) | ||
val testVectorJwk = vector.input.key | ||
|
||
val ed25519Jwk = Jwk( | ||
kty = testVectorJwk.kty, | ||
crv = testVectorJwk.crv, | ||
d = testVectorJwk.d, | ||
x = testVectorJwk.x, | ||
alg = null, | ||
y = null | ||
) | ||
val signer = Ed25519Signer(ed25519Jwk) | ||
|
||
if (vector.errors == true) { | ||
assertThrows(Exception::class.java) { | ||
signer.sign(inputByteArray) | ||
} | ||
} else { | ||
val signedByteArray = signer.sign(inputByteArray) | ||
val signedHex = byteArrayToHexString(signedByteArray) | ||
assertEquals(vector.output, signedHex) | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun verify() { | ||
val typeRef = object : TypeReference<TestVectors<VerifyTestInput, Boolean>>() {} | ||
val testVectors = | ||
Json.jsonMapper.readValue(File("../../web5-spec/test-vectors/crypto_ed25519/verify.json"), typeRef) | ||
|
||
testVectors.vectors.forEach { vector -> | ||
val inputByteArray = hexStringToByteArray(vector.input.data) | ||
val signatureByteArray = hexStringToByteArray(vector.input.signature) | ||
val testVectorJwk = Json.jsonMapper.convertValue(vector.input.key, TestVectorJwk::class.java) | ||
|
||
val ed25519Jwk = Jwk( | ||
kty = testVectorJwk.kty, | ||
crv = testVectorJwk.crv, | ||
d = null, | ||
x = testVectorJwk.x, | ||
alg = null, | ||
y = null | ||
) | ||
val verifier = Ed25519Verifier(ed25519Jwk) | ||
|
||
if (vector.errors == true) { | ||
assertThrows(Exception::class.java) { | ||
verifier.verify(inputByteArray, signatureByteArray) | ||
} | ||
} else { | ||
val verified = verifier.verify(inputByteArray, signatureByteArray) | ||
assertEquals(vector.output, verified) | ||
} | ||
} | ||
} | ||
|
||
private fun hexStringToByteArray(s: String): ByteArray { | ||
val len = s.length | ||
val data = ByteArray(len / 2) | ||
for (i in 0 until len step 2) { | ||
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte() | ||
} | ||
return data | ||
} | ||
|
||
private fun byteArrayToHexString(bytes: ByteArray): String { | ||
return bytes.joinToString("") { "%02x".format(it) } | ||
} | ||
} | ||
|
||
@Nested | ||
inner class Web5TestVectorsDidJwk { | ||
|
||
// This is so we can parse the test vector converting metadata errors in the test vectors like invalidDid to the INVALID-DID enum | ||
internal inner class CustomEnumNamingStrategy : PropertyNamingStrategy() { | ||
override fun nameForField(config: MapperConfig<*>?, field: AnnotatedField?, defaultName: String?): String? { | ||
if (field?.type?.isEnumType == true) { | ||
return convertToUpperSnakeCase(defaultName) | ||
} | ||
return defaultName | ||
} | ||
|
||
override fun nameForGetterMethod(config: MapperConfig<*>?, method: AnnotatedMethod?, defaultName: String?): String { | ||
if (method?.rawReturnType?.isEnum == true) { | ||
return convertToUpperSnakeCase(defaultName) | ||
} | ||
return defaultName!! | ||
} | ||
|
||
override fun nameForSetterMethod(config: MapperConfig<*>?, method: AnnotatedMethod?, defaultName: String?): String { | ||
if (method?.rawParameterTypes?.firstOrNull()?.isEnum == true) { | ||
return convertToUpperSnakeCase(defaultName) | ||
} | ||
return defaultName!! | ||
} | ||
|
||
private fun convertToUpperSnakeCase(input: String?): String { | ||
return input?.replace("([a-z])([A-Z]+)".toRegex(), "$1_$2")?.uppercase() ?: "" | ||
} | ||
} | ||
|
||
val jsonMapper: ObjectMapper = jacksonObjectMapper() | ||
.registerKotlinModule() | ||
.findAndRegisterModules() | ||
.setPropertyNamingStrategy(CustomEnumNamingStrategy()) | ||
.setSerializationInclusion(JsonInclude.Include.NON_NULL) | ||
.disable((DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) | ||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) | ||
|
||
@Test | ||
fun resolve() { | ||
val typeRef = object : TypeReference<TestVectors<String, DidJwkResolveTestOutput>>() {} | ||
|
||
val testVectors = | ||
jsonMapper.readValue(File("../../web5-spec/test-vectors/did_jwk/resolve.json"), typeRef) | ||
|
||
testVectors.vectors.forEach { vector -> | ||
if (vector.errors == true) { | ||
val resolvedDid = web5.sdk.dids.methods.jwk.DidJwk.resolve(vector.input) | ||
|
||
assertTrue(resolvedDid.resolutionMetadata.error != null) | ||
|
||
// TODO: parse resolutionMetadata from the test vector correctly | ||
// assertEquals(resolvedDid.resolutionMetadata, vector.output!!.didResolutionMetadata) | ||
} else { | ||
val resolvedDid = web5.sdk.dids.methods.jwk.DidJwk.resolve(vector.input) | ||
|
||
assertEquals(resolvedDid.document!!.id, vector.output!!.didDocument!!.id) | ||
assertEquals(resolvedDid.document!!.verificationMethod, vector.output.didDocument!!.verificationMethod) | ||
assertEquals(resolvedDid.document!!.authentication, vector.output.didDocument.authentication) | ||
assertEquals(resolvedDid.document!!.assertionMethod, vector.output.didDocument.assertionMethod) | ||
assertEquals(resolvedDid.document!!.capabilityDelegation, vector.output.didDocument.capabilityDelegation) | ||
assertEquals(resolvedDid.document!!.capabilityInvocation, vector.output.didDocument.capabilityInvocation) | ||
} | ||
} | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
bound/kt/src/test/kotlin/web5/sdk/crypto/verifiers/Ed25519VerifierTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package web5.sdk.crypto.verifiers | ||
|
||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.Assertions.assertTrue | ||
import org.junit.jupiter.api.Assertions.assertFalse | ||
import web5.sdk.crypto.keys.Jwk | ||
import web5.sdk.crypto.signers.Ed25519Signer | ||
import web5.sdk.rust.ed25519GeneratorGenerate as rustCoreEd25519GeneratorGenerate | ||
|
||
class Ed25519VerifierTest { | ||
|
||
@Test | ||
fun `test verifier with valid signature`() { | ||
val privateJwk = rustCoreEd25519GeneratorGenerate() | ||
val ed25519Signer = Ed25519Signer(Jwk.fromRustCoreJwkData(privateJwk)) | ||
|
||
val message = "abc".toByteArray() | ||
val signature = ed25519Signer.sign(message) | ||
|
||
val ed25519Verifier = Ed25519Verifier(Jwk.fromRustCoreJwkData(privateJwk)) | ||
val isValid = ed25519Verifier.verify(message, signature) | ||
|
||
assertTrue(isValid, "Signature should be valid") | ||
} | ||
|
||
@Test | ||
fun `test verifier with invalid signature`() { | ||
val privateJwk = rustCoreEd25519GeneratorGenerate() | ||
val ed25519Signer = Ed25519Signer(Jwk.fromRustCoreJwkData(privateJwk)) | ||
|
||
val message = "abc".toByteArray() | ||
val signature = ed25519Signer.sign(message) | ||
|
||
val modifiedMessage = "abcd".toByteArray() | ||
|
||
val ed25519Verifier = Ed25519Verifier(Jwk.fromRustCoreJwkData(privateJwk)) | ||
val isValid = ed25519Verifier.verify(modifiedMessage, signature) | ||
|
||
assertFalse(isValid, "Signature should be invalid") | ||
} | ||
} |
Oops, something went wrong.