-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add sign and verify test vectors #292
Changes from 3 commits
1017532
3217780
ce1cfa7
5d194d2
2ce2eed
3090298
1fead60
7949cbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -207,4 +207,3 @@ impl From<InnerWeb5Error> for Web5Error { | |
} | ||
|
||
pub type Result<T> = std::result::Result<T, Web5Error>; | ||
|
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) | ||
} | ||
|
||
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); | ||
} | ||
} |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package web5.sdk | ||
|
||
import com.fasterxml.jackson.core.type.TypeReference | ||
import org.junit.jupiter.api.Test | ||
import java.io.File | ||
import org.junit.jupiter.api.Assertions.* | ||
import web5.sdk.crypto.keys.Jwk | ||
import web5.sdk.crypto.signers.Ed25519Signer | ||
import web5.sdk.crypto.verifiers.Ed25519Verifier | ||
|
||
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, | ||
) | ||
|
||
class Web5TestVectorsCryptoEd25519 { | ||
|
||
data class SignTestInput( | ||
val data: String, | ||
val key: TestVectorJwk, | ||
) | ||
|
||
data class TestVectorJwk( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know we're not testing the JWK in this test, but ideally we could use the real There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup after 297 is in we can use it |
||
val crv: String, | ||
val d: String?, | ||
val kid: String, | ||
val kty: String, | ||
val x: String | ||
) | ||
|
||
@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) | ||
} | ||
} | ||
} | ||
|
||
data class VerifyTestInput( | ||
val key: Map<String, Any>, | ||
val signature: String, | ||
val data: String | ||
) | ||
|
||
@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) } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
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.signers.Ed25519Signer | ||
import web5.sdk.rust.ed25519GeneratorGenerate as rustCoreEd25519GeneratorGenerate | ||
|
||
class Ed25519VerifierTest { | ||
|
||
@Test | ||
fun `test verifier with valid signature`() { | ||
val privateJwk = rustCoreEd25519GeneratorGenerate() | ||
val ed25519Signer = Ed25519Signer(privateJwk) | ||
|
||
val message = "abc".toByteArray() | ||
val signature = ed25519Signer.sign(message) | ||
|
||
val ed25519Verifier = Ed25519Verifier(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(privateJwk) | ||
|
||
val message = "abc".toByteArray() | ||
val signature = ed25519Signer.sign(message) | ||
|
||
val modifiedMessage = "abcd".toByteArray() | ||
|
||
val ed25519Verifier = Ed25519Verifier(privateJwk) | ||
val isValid = ed25519Verifier.verify(modifiedMessage, signature) | ||
|
||
assertFalse(isValid, "Signature should be invalid") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,4 +9,4 @@ pub mod rfc3339; | |
#[cfg(test)] | ||
mod test_helpers; | ||
#[cfg(test)] | ||
mod test_vectors; | ||
mod test_vectors; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ pub struct TestVector<I, O> { | |
pub description: String, | ||
pub input: I, | ||
pub output: O, | ||
pub errors: Option<bool>, | ||
} | ||
|
||
#[derive(Debug, serde::Deserialize)] | ||
|
@@ -207,4 +208,100 @@ mod test_vectors { | |
} | ||
} | ||
} | ||
|
||
mod crypto_ed25519 { | ||
use super::*; | ||
use crate::crypto::dsa::ed25519::{Ed25519Signer, Ed25519Verifier}; | ||
use crate::crypto::dsa::{Signer, Verifier}; | ||
use crate::crypto::jwk::Jwk; | ||
|
||
#[derive(Debug, serde::Deserialize)] | ||
struct SignVectorInput { | ||
data: String, | ||
key: Jwk, | ||
} | ||
|
||
#[derive(Debug, serde::Deserialize)] | ||
struct VerifyVectorInput { | ||
data: String, | ||
key: Jwk, | ||
signature: String, | ||
} | ||
|
||
#[test] | ||
fn sign() { | ||
let path = "crypto_ed25519/sign.json"; | ||
let vectors: TestVectorFile<SignVectorInput, Option<String>> = | ||
TestVectorFile::load_from_path(path); | ||
|
||
for vector in vectors.vectors { | ||
let input = vector.input; | ||
let expected_output = vector.output; | ||
|
||
let signer = Ed25519Signer::new(input.key); | ||
|
||
let data = hex_string_to_byte_array(input.data.to_string()); | ||
let result = signer.sign(&data); | ||
|
||
if matches!(vector.errors, Some(true)) { | ||
assert!(result.is_err(), "Expected an error, but signing succeeded"); | ||
} else { | ||
let signature = result.expect("Signing should not fail"); | ||
|
||
// Convert the signature to a hex string | ||
let signature_hex = byte_array_to_hex_string(&signature); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm surprised this works. ed25519 signatures are different every time because they use a random nonce to prevent computing the private key if the same private key is used for multiple signatures There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh no.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we are good - https://cryptobook.nakov.com/digital-signatures/eddsa-and-ed25519 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see Section 4.2.5 on Deterministic Nonce Generation
|
||
|
||
assert_eq!( | ||
signature_hex, | ||
expected_output.unwrap(), | ||
"Signature does not match expected output" | ||
); | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn verify() { | ||
let path = "crypto_ed25519/verify.json"; | ||
let vectors: TestVectorFile<VerifyVectorInput, bool> = | ||
TestVectorFile::load_from_path(path); | ||
|
||
for vector in vectors.vectors { | ||
let input = vector.input; | ||
let expected_output = vector.output; | ||
|
||
let verifier = Ed25519Verifier::new(input.key); | ||
|
||
let data = hex_string_to_byte_array(input.data.to_string()); | ||
let signature = hex_string_to_byte_array(input.signature.to_string()); | ||
|
||
let result = verifier.verify(&data, &signature); | ||
|
||
let is_valid = result.expect("Verification should not fail"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the difference between Err() and Ok(false) for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. chad says: and yea then err is an exception basically if I'm understnading rust correctly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh and as far as the implementation yea just verified false is invalid signature vs catastrophic failure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
assert_eq!( | ||
is_valid, expected_output, | ||
"Verification result does not match expected output: {}", | ||
vector.description | ||
); | ||
} | ||
} | ||
|
||
fn hex_string_to_byte_array(s: String) -> Vec<u8> { | ||
s.chars() | ||
.collect::<Vec<char>>() | ||
.chunks(2) | ||
.map(|chunk| { | ||
let hex_pair: String = chunk.iter().collect(); | ||
u8::from_str_radix(&hex_pair, 16).unwrap() | ||
}) | ||
.collect() | ||
} | ||
|
||
fn byte_array_to_hex_string(bytes: &[u8]) -> String { | ||
bytes | ||
.iter() | ||
.map(|b| format!("{:02x}", b)) | ||
.collect::<String>() | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think with the latest on
main
you'll need to rebase and then this'll be...