mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-28 16:32:06 +00:00
- core/derive/ssh.ts: BIP-85 app 32 entropy used directly as the Ed25519 private key, serialized as PKCS#8 and SubjectPublicKeyInfo PEM. Both DER shapes are fixed for Ed25519, so they are emitted from literal prefixes rather than adding an ASN.1 dependency to the crypto path. - Output matches Python's `cryptography` byte-for-byte across two seeds and three indices, including PEM line wrapping. - CLI: `entry add ssh`, reveal (private key PEM), and `entry ssh-public` which prints the public key without touching secret material. The OpenSSH authorized_keys line is a TS-only convenience -- Python emits only PEM, which ssh(1) cannot consume directly -- and is marked as such in the compatibility matrix. - Cross-impl phases A and B now include an ssh entry in both directions. One harness fix: PEM values carry a trailing newline that the CLI capture strips, so the cross-impl comparison normalizes trailing whitespace. The unit tests still compare exactly. 170 core + 46 CLI tests green; 16/16 cross-implementation checks green. Co-Authored-By: Claude Fable 5 <[email protected]>
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
/** SSH key derivation parity: PEM output must match Python byte-for-byte. */
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
import { sshKeyCases, mnemonics } from "@seedpass/test-vectors";
|
|
import {
|
|
deriveSshKeyPair,
|
|
deriveSshEntropy,
|
|
sshPublicKeyOpenSsh,
|
|
Bip85,
|
|
bytesToHex,
|
|
} from "@seedpass/core";
|
|
|
|
describe("SSH key derivation", () => {
|
|
it.each(sshKeyCases)("$mnemonic_id index $index", (c) => {
|
|
const mnemonic = mnemonics[c.mnemonic_id]!;
|
|
const pair = deriveSshKeyPair(mnemonic, c.index);
|
|
expect(bytesToHex(pair.privateKey)).toBe(c.entropy_hex);
|
|
expect(pair.privateKeyPem).toBe(c.private_key_pem);
|
|
expect(pair.publicKeyPem).toBe(c.public_key_pem);
|
|
});
|
|
|
|
it("uses BIP-85 app 32 for its entropy", () => {
|
|
const c = sshKeyCases[0]!;
|
|
const bip85 = Bip85.fromMnemonic(mnemonics[c.mnemonic_id]!);
|
|
expect(bytesToHex(deriveSshEntropy(bip85, c.index))).toBe(c.entropy_hex);
|
|
});
|
|
|
|
it("emits an OpenSSH public key line (TS-only convenience)", () => {
|
|
const pair = deriveSshKeyPair(mnemonics["abandon12"]!, 0);
|
|
const line = sshPublicKeyOpenSsh(pair.publicKey, "seedpass");
|
|
expect(line).toMatch(/^ssh-ed25519 [A-Za-z0-9+/=]+ seedpass$/);
|
|
// The blob embeds the same 32-byte key the PEM carries
|
|
const blob = Buffer.from(line.split(" ")[1]!, "base64");
|
|
expect(blob.subarray(blob.length - 32).toString("hex")).toBe(
|
|
bytesToHex(pair.publicKey),
|
|
);
|
|
});
|
|
});
|