Skip to content
1Claw Academy
Curriculum/Security Foundations4 minBeginner · Lesson 5 of 19

Lab: sign, verify, then tamper

Lab

Generate an Ed25519 keypair, sign a message, verify it with the public key alone, and watch one changed character destroy the signature.

The previous lesson claimed that a signature proves authorship and detects modification, and that verification needs no secret. Three minutes of openssl turns all three claims into things you have seen.

  1. 1

    Make a scratch directory and generate an Ed25519 private key.

    bash
    mkdir -p /tmp/siglab && cd /tmp/siglab
    openssl genpkey -algorithm ed25519 -out private.pem
  2. 2

    Derive the public key. This is the half you hand out, and it cannot be used to sign.

    bash
    openssl pkey -in private.pem -pubout -out public.pem
    cat public.pem
  3. 3

    Write a message worth being careful about, and sign it with the private key.

    bash
    printf 'transfer 10 ETH to 0xAlice' > msg.txt
    openssl pkeyutl -sign -inkey private.pem -rawin -in msg.txt -out msg.sig
    wc -c < msg.sig   # 64 bytes, regardless of message length
  4. 4

    Verify using only the public key. Note what you did not need: the private key, a shared secret, or a call to anyone.

    bash
    openssl pkeyutl -verify -pubin -inkey public.pem -rawin -in msg.txt -sigfile msg.sig
    # -> Signature Verified Successfully
  5. 5

    Now tamper. Change the recipient and check the original signature against it.

    bash
    printf 'transfer 10 ETH to 0xMallory' > tampered.txt
    openssl pkeyutl -verify -pubin -inkey public.pem -rawin -in tampered.txt -sigfile msg.sig
    # -> Signature Verification Failure
  6. 6

    Confirm the message was never hidden. The signature protects it; it does not conceal it.

    bash
    cat msg.txt
    # -> transfer 10 ETH to 0xAlice
  7. 7

    One more check, and it is the one that matters later. Generate a second, unrelated keypair and verify the original signature against it.

    bash
    openssl genpkey -algorithm ed25519 -out other.pem
    openssl pkey -in other.pem -pubout -out other.pub
    
    openssl pkeyutl -verify -pubin -inkey other.pub \
      -rawin -in msg.txt -sigfile msg.sig
  8. 8

    It fails, which is correct and is not the interesting part. The interesting part is what that implies.

    text
    Signature Verification Failure
  9. 9

    Clean up.

    bash
    cd /tmp && rm -rf siglab

A signature carries no indication of which key produced it. Verification answers "was this signed by the key I am holding", and it can only answer that because you chose which key to check against. Choose the wrong one and a perfectly genuine signature is rejected; fail to pin down which one is correct and you will eventually accept a genuine signature from the wrong signer.

Tip

That is the whole problem the next lessons keep returning to. A JWT names its key in the kid header and its intended recipient in aud, and a verifier that checks the signature without checking those is asking the wrong question correctly.

The signature stayed 64 bytes whatever you signed, because signing operates on a fixed-length hash of the message rather than the message itself. That is also why the hash function's collision resistance is part of the security of the scheme and not a separate concern.

Tip

You verified with a public key and could not have signed with it. That asymmetry is what makes federated trust possible: a relying party validates 1Claw-issued tokens against published JWKS without holding any shared secret.

Watch out

Now imagine signing msg.sig's contents without ever seeing msg.txt. That is blind signing, and it is why raw digest signing is gated behind a human-set flag in the Intents API.

Check your understanding

3 questions
1

The signature was 64 bytes for a short message. What would it be for a 4GB file?

2

You verified the signature with public.pem. Could you have produced a signature with it?

3

After tampering, verification failed. What did that demonstrate?