// Interop spike: X25519 + HKDF-SHA256 + Ed25519 with fixed inputs (Go stdlib only). // Prints the same key=value lines as the C and C++ programs so the outputs can be diffed. package main import ( "bytes" "crypto/ecdh" "crypto/ed25519" "crypto/hkdf" "crypto/sha256" "encoding/hex" "fmt" "os" ) const ( boardPriv = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" backendEph = "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" signingSeed = "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" nonce = "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" clientID = "e1c9f0a2b3d4c5b6a79881726354453f" fn = "FN-000042" sn = "SN000123" ) func die(msg string, err error) { fmt.Fprintf(os.Stderr, "FAIL: %s: %v\n", msg, err) os.Exit(1) } func unhex(s string) []byte { b, err := hex.DecodeString(s) if err != nil { die("hex", err) } return b } func x25519Pub(priv []byte) []byte { k, err := ecdh.X25519().NewPrivateKey(priv) if err != nil { die("x25519 priv", err) } return k.PublicKey().Bytes() } func x25519Shared(priv, peer []byte) []byte { k, err := ecdh.X25519().NewPrivateKey(priv) if err != nil { die("x25519 priv", err) } p, err := ecdh.X25519().NewPublicKey(peer) if err != nil { die("x25519 pub", err) } s, err := k.ECDH(p) if err != nil { die("x25519 derive", err) } return s } func derive(ikm, salt []byte, info string, n int) []byte { out, err := hkdf.Key(sha256.New, ikm, salt, info, n) if err != nil { die("hkdf", err) } return out } func expect(name string, got []byte, want string) { if hex.EncodeToString(got) != want { fmt.Fprintf(os.Stderr, "FAIL: %s\n got %x\n want %s\n", name, got, want) os.Exit(1) } } func kat() { // RFC 7748 section 6.1 alicePriv := unhex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a") bobPriv := unhex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb") alicePub := x25519Pub(alicePriv) bobPub := x25519Pub(bobPriv) expect("rfc7748 alice pub", alicePub, "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a") expect("rfc7748 bob pub", bobPub, "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f") expect("rfc7748 shared a", x25519Shared(alicePriv, bobPub), "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742") expect("rfc7748 shared b", x25519Shared(bobPriv, alicePub), "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742") fmt.Println("kat.x25519=ok") // RFC 5869 test case 1 ikm := bytes.Repeat([]byte{0x0b}, 22) salt := unhex("000102030405060708090a0b0c") info := string(unhex("f0f1f2f3f4f5f6f7f8f9")) expect("rfc5869 tc1", derive(ikm, salt, info, 42), "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865") fmt.Println("kat.hkdf=ok") // RFC 8032 section 7.1, test 1 (empty message) key := ed25519.NewKeyFromSeed(unhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")) expect("rfc8032 pub", key.Public().(ed25519.PublicKey), "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a") expect("rfc8032 sig", ed25519.Sign(key, []byte{}), "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b") fmt.Println("kat.ed25519=ok") } func password(shared []byte, id string) string { return hex.EncodeToString(derive(shared, unhex(nonce), "robot-market/mqtt-pw/v1|"+id, 32)) } func main() { kat() b := unhex(boardPriv) s := unhex(backendEph) boardPub := x25519Pub(b) backendPub := x25519Pub(s) fmt.Printf("board_pub=%x\n", boardPub) fmt.Printf("backend_eph_pub=%x\n", backendPub) shared := x25519Shared(s, boardPub) // backend side fmt.Printf("shared_secret=%x\n", shared) if !bytes.Equal(shared, x25519Shared(b, backendPub)) { // board side die("shared secret mismatch", fmt.Errorf("board and backend differ")) } fmt.Println("shared_secret_peer_match=ok") fmt.Printf("password_fn=%s\n", password(shared, fn)) fmt.Printf("password_sn=%s\n", password(shared, sn)) // Backend signs S || B || nonce || clientID. key := ed25519.NewKeyFromSeed(unhex(signingSeed)) pub := key.Public().(ed25519.PublicKey) msg := append(append(append(append([]byte{}, backendPub...), boardPub...), unhex(nonce)...), []byte(clientID)...) sig := ed25519.Sign(key, msg) fmt.Printf("signing_pub=%x\n", []byte(pub)) fmt.Printf("signed_message=%x\n", msg) fmt.Printf("signature=%x\n", sig) if !ed25519.Verify(pub, msg, sig) { die("signature verify", fmt.Errorf("invalid")) } fmt.Println("signature_verify=ok") }