Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ghostsig

Verifiable invisible signatures for plain text. Sign a document with your Ed25519 private key and the signature is embedded as a string of invisible Unicode characters — anyone with your public key can verify the text is authentic and unmodified, and nobody reading it sees a thing.

Pure Node.js (>= 20), zero dependencies, no build step.

Quick start

# one-time: generate a keypair in ~/.config/ghostsig
node bin/ghostsig.js keygen

# sign (stdin -> stdout)
echo "I wrote this." | node bin/ghostsig.js sign > signed.txt

# verify
node bin/ghostsig.js verify signed.txt        # OK: signature valid (key id 1a2b3c4d)

# show embedded metadata without verifying
node bin/ghostsig.js inspect signed.txt

# remove the signature
node bin/ghostsig.js strip signed.txt

Install globally with npm link (or npm i -g .) to get the ghostsig command on your PATH.

Key locations

keygen writes to $GHOSTSIG_DIR, else $XDG_CONFIG_HOME/ghostsig, else ~/.config/ghostsig:

file contents mode
ghostsig.pem private key (PKCS8) 0600
ghostsig.pub public key (SPKI) 0644

All commands accept -k <file> to use a specific key instead. Keys are standard PEM, so you can also generate them with openssl genpkey -algorithm ed25519 if you prefer.

How it works

  1. Canonicalize the text (see below) and sign it with Ed25519.
  2. Frame the payload: magic "GS" (2B) ‖ version (1B) ‖ key id (4B) ‖ signature (64B) — 71 bytes. The key id is the first 4 bytes of the SHA-256 of the raw public key, so verifiers can tell which key signed.
  3. Encode: base64 the payload (96 chars), then map each char c to Unicode codepoint U+E0000 + c — the Tags block (U+E0020–U+E007E), which renders as nothing in virtually all fonts and survives copy-paste, email, Slack, and most CMSes.
  4. Append the invisible string to the end of the text.

Verification reverses this: extract tag characters → decode → canonicalize the remaining visible text → check the Ed25519 signature and key id.

Canonicalization

Editors routinely normalize documents in transit, which would break a naive byte-for-byte signature. By default, ghostsig signs/verifies the text after:

  1. Unicode NFC normalization
  2. CRLF / CR → LF line endings
  3. Stripping trailing spaces and tabs at end of lines

So verify still passes after a document round-trips through Slack or a web form. Pass --raw to sign/verify to skip this and sign the exact bytes.

Library API

import { generateKeyPair, sign, verify, extract, strip } from 'ghostsig';

const { publicKey, privateKey } = generateKeyPair();

const signed = sign('I wrote this.', privateKey);
verify(signed, publicKey);
// { ok: true, keyId: '1a2b3c4d' }

verify('tampered' + signed.slice(8), publicKey);
// { ok: false, reason: 'invalid-signature', keyId: '1a2b3c4d' }

extract(signed);  // { version: 1, keyId: '1a2b3c4d', signature: <Buffer> }
strip(signed);    // 'I wrote this.'

verify never throws on bad input; reason is one of 'no-signature', 'malformed', 'key-id-mismatch', 'invalid-signature'. sign and verify accept { raw: true } to skip canonicalization. Key helpers (saveKeyPair, loadPrivateKey, loadPublicKey, fingerprint, configDir) are also exported.

Exit codes

code meaning
0 success / signature valid
1 verification failed or no signature found
2 usage error, missing key, or I/O error

Limitations (read before relying on this)

  • It proves authenticity when present — nothing when absent. Anyone who knows the scheme can strip the characters with one line of code. This is an opt-in authenticity watermark, not DRM.
  • Signatures are fragile across transforms. Retyping, screenshots/OCR, printing, and some sanitizers destroy the characters. Test your actual distribution channel.
  • The content is not encrypted or hidden — only signed.
  • Security scanners may flag it. Unicode Tag characters are associated with "ASCII smuggling" prompt-injection research, so some tools warn on documents containing them.

Development

npm test    # node:test suite, no dependencies to install

About

Verifiable invisible signatures for plain text — embed an Ed25519 signature as invisible Unicode characters; anyone with the public key can verify authenticity. CLI + library, pure Node.js, zero dependencies.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages