mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 19:53:08 +00:00
feat(marmot): Ed25519.keyPairFromSeed
Some MLS stacks store Ed25519 signing keys as the 32-byte seed only, while Quartz uses the 64-byte seed||public form. keyPairFromSeed rebuilds the pair from the seed (RFC 8032 §5.1.5) with each platform's existing derivePublicKey.
This commit is contained in:
+6
@@ -128,6 +128,12 @@ actual object Ed25519 {
|
||||
return privateKey.copyOfRange(SEED_LENGTH, SEED_LENGTH * 2)
|
||||
}
|
||||
|
||||
actual fun keyPairFromSeed(seed: ByteArray): Ed25519KeyPair {
|
||||
require(seed.size == SEED_LENGTH) { "Seed must be 32 bytes" }
|
||||
val publicKey = derivePublicKey(seed)
|
||||
return Ed25519KeyPair(seed + publicKey, publicKey)
|
||||
}
|
||||
|
||||
// --- Internal operations ---
|
||||
|
||||
/** Derive Ed25519 public key from 32-byte seed. */
|
||||
|
||||
@@ -64,6 +64,12 @@ expect object Ed25519 {
|
||||
* @return 32-byte public key
|
||||
*/
|
||||
fun publicFromPrivate(privateKey: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* Rebuild a key pair from a 32-byte seed (RFC 8032 §5.1.5), for keys stored as seeds only.
|
||||
* @return pair of (privateKey: 64 bytes seed+public, publicKey: 32 bytes)
|
||||
*/
|
||||
fun keyPairFromSeed(seed: ByteArray): Ed25519KeyPair
|
||||
}
|
||||
|
||||
data class Ed25519KeyPair(
|
||||
|
||||
+6
@@ -114,6 +114,12 @@ actual object Ed25519 {
|
||||
return privateKey.copyOfRange(SEED_LENGTH, SEED_LENGTH * 2)
|
||||
}
|
||||
|
||||
actual fun keyPairFromSeed(seed: ByteArray): Ed25519KeyPair {
|
||||
require(seed.size == SEED_LENGTH) { "Seed must be 32 bytes" }
|
||||
val publicKey = derivePublicKey(seed)
|
||||
return Ed25519KeyPair(seed + publicKey, publicKey)
|
||||
}
|
||||
|
||||
private fun derivePublicKey(seed: ByteArray): ByteArray {
|
||||
val d = sha512(seed)
|
||||
d[0] = (d[0].toInt() and 248).toByte()
|
||||
|
||||
@@ -114,4 +114,21 @@ class Ed25519Test {
|
||||
) {
|
||||
kotlin.test.assertContentEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyPairFromSeedMatchesRfc8032TestVector1() {
|
||||
val seed = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60".hexToByteArray()
|
||||
val kp = Ed25519.keyPairFromSeed(seed)
|
||||
assertContentEquals("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a".hexToByteArray(), kp.publicKey)
|
||||
assertContentEquals(seed + kp.publicKey, kp.privateKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyPairFromSeedSignsLikeTheGeneratedPair() {
|
||||
val generated = Ed25519.generateKeyPair()
|
||||
val rebuilt = Ed25519.keyPairFromSeed(generated.privateKey.copyOfRange(0, 32))
|
||||
assertEquals(generated, rebuilt)
|
||||
val msg = "hi".encodeToByteArray()
|
||||
assertTrue(Ed25519.verify(msg, Ed25519.sign(msg, rebuilt.privateKey), generated.publicKey))
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -114,6 +114,12 @@ actual object Ed25519 {
|
||||
return privateKey.copyOfRange(SEED_LENGTH, SEED_LENGTH * 2)
|
||||
}
|
||||
|
||||
actual fun keyPairFromSeed(seed: ByteArray): Ed25519KeyPair {
|
||||
require(seed.size == SEED_LENGTH) { "Seed must be 32 bytes" }
|
||||
val publicKey = derivePublicKey(seed)
|
||||
return Ed25519KeyPair(seed + publicKey, publicKey)
|
||||
}
|
||||
|
||||
private fun derivePublicKey(seed: ByteArray): ByteArray {
|
||||
val d = sha512(seed)
|
||||
d[0] = (d[0].toInt() and 248).toByte()
|
||||
|
||||
Reference in New Issue
Block a user