30817:schnorr-key-card-formats

Schnorr Key Card Formats

hzrd149

published
2026-10-02

SKC: Schnorr Key Card Formats

draft optional

SKC defines formats for storing a Nostr secret key on an ISO/IEC 7811 magnetic-stripe card:

  • SKC1 stores the raw secret key on Track 1. It is simple and widely readable, but the card is a bearer instrument.
  • SKC2 stores the exact 91-byte encrypted-key payload defined by NIP-49 across all three tracks.

The value protected by either format is the raw 32-byte secp256k1 secret scalar represented by an nsec, not the Bech32-encoded nsec text.

Physical requirements

  • Card: ISO/IEC 7811 magnetic-stripe card
  • Stripe: HiCo recommended
  • Track 1: 210 bpi, alphanumeric
  • Track 2: 75 bpi, numeric
  • Track 3: 210 bpi, numeric

Lengths in this document describe logical track data and exclude the track-specific start sentinel, end sentinel, and LRC added by the encoder. Reader hardware commonly validates and removes the LRC before returning track data.

SKC1: Unencrypted key

SKC1 uses uppercase hexadecimal for compatibility with common Track 1 tooling. Tracks 2 and 3 MUST be blank.

Layout

Track Field Length Content
1 Magic and version 4 SKC1
1 Secret key 64 Raw 32-byte secret key as uppercase hexadecimal
1 Checksum 8 First four bytes of SHA-256(secret_key) as uppercase hexadecimal

The Track 1 data is exactly 76 characters:

SKC1<64 uppercase hexadecimal key characters><8 uppercase hexadecimal checksum characters>

A keyboard-wedge reader will commonly include the sentinels:

%SKC1<key><checksum>?

Test vector

The following publicly known key MUST NOT be used for real funds or identities:

Secret key: 67DEA2ED018072D675F5415ECFAED7D2597555E202D85B3D65EA4E58D2D92FFA
Checksum:   37BD6F6E
Track 1:    SKC167DEA2ED018072D675F5415ECFAED7D2597555E202D85B3D65EA4E58D2D92FFA37BD6F6E

Writing

  1. Obtain a valid 32-byte Nostr secret key sk.
  2. Verify that 0 < bytes_to_integer(sk) < n, where n is the secp256k1 group order.
  3. Encode sk as 64 uppercase hexadecimal characters.
  4. Append the first four bytes of SHA-256(sk) as eight uppercase hexadecimal characters.
  5. Write SKC1 followed by the resulting 72 key and checksum characters on Track 1.
  6. Leave Tracks 2 and 3 blank.

Reading

  1. Require exactly 76 Track 1 data characters beginning with SKC1.

  2. Reject key or checksum characters outside 0-9 and A-F.

  3. Decode the 64-character key field to exactly 32 bytes sk.

  4. Verify that 0 < bytes_to_integer(sk) < n, where:

    n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
    
  5. Compare the checksum with the first four bytes of SHA-256(sk).

  6. Accept the key only if every check succeeds.

SKC2: NIP-49 encrypted key

SKC2 stores the exact 91-byte CIPHERTEXT_CONCATENATION defined by NIP-49. It does not store the longer Bech32 ncryptsec text. Software can recreate a standard ncryptsec by Bech32-encoding the recovered bytes with the ncryptsec human-readable prefix.

SKC2 requires a reader and writer that support all three tracks. A reader that omits Track 3 cannot read SKC2 cards.

Byte layout

Let payload be the 91-byte NIP-49 payload. Split it without changing the byte order:

part_a = payload[0:44]   # 44 bytes
part_b = payload[44:51]  # 7 bytes
part_c = payload[51:91]  # 40 bytes

The layout leaves several characters of headroom on every track:

Track Field Length Content
1 Magic and version 4 SKC2
1 Part A 66 Base45 encoding of part_a
2 Version marker 2 02
2 Part B 17 Fixed-width decimal encoding of part_b
2 Transport checksum 15 Fixed-width decimal encoding of the first six bytes of SHA-256(payload)
3 Part C 97 Fixed-width decimal encoding of part_c

Total logical data lengths are 70 characters on Track 1, 34 digits on Track 2, and 97 digits on Track 3.

Base45 encoding

SKC2 uses the Base45 algorithm from RFC 9285 with this ordered, Track 1-safe alphabet:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:@_

The replacement alphabet avoids spaces and the %, ?, and ^ Track 1 framing characters. Each pair of input bytes is encoded as three Base45 characters. Therefore, the 44-byte part_a always encodes to exactly 66 characters.

For each pair of bytes a, b, calculate:

x  = 256 * a + b
c0 = x mod 45
c1 = floor(x / 45) mod 45
c2 = floor(x / 2025)

Emit the alphabet characters at indexes c0, c1, and c2, in that order. A decoder MUST reject a three-character group whose decoded value is greater than 65535.

Decimal encoding

Decimal fields represent unsigned big-endian integers and MUST be left-padded with zeroes to their specified widths:

  • A 7-byte part_b becomes exactly 17 decimal digits. A decoder MUST reject a value greater than or equal to 2^56.
  • The 6-byte transport checksum becomes exactly 15 decimal digits. A decoder MUST reject a value greater than or equal to 2^48.
  • A 40-byte part_c becomes exactly 97 decimal digits. A decoder MUST reject a value greater than or equal to 2^320.

Fixed widths preserve leading zero bytes.

Test vector

This example uses the NIP-49 test vector encrypted with the password nostr. It MUST NOT be used for real funds or identities.

ncryptsec: ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p

Track 1: SKC2XB0CLA+YO:5B8QFZ+I@IG6$NCVCXUO4F0F.R_GPTIRUN49U82QG1K1/YNP3UD9L440
Track 2: 0265443156511849278265095537737482
Track 3: 1244050960251565704501305373493987938715614508987092947983209949753341979419105711517054427654006

Writing

  1. Produce or decode a valid NIP-49 ncryptsec value according to NIP-49.
  2. Require the decoded payload to be exactly 91 bytes and its NIP-49 version byte to be 0x02.
  3. Split and encode the payload as specified above.
  4. Compute the transport checksum over the complete 91-byte payload.
  5. Write all three tracks.
  6. Read the card back and complete the full SKC2 validation procedure before issuing it.

The NIP-49 key-security byte MUST reflect how the key was actually handled. Writers MUST NOT use 0x01 unless they can guarantee the stronger handling claim defined by NIP-49.

Reading

  1. Require Track 1, Track 2, and Track 3 data.
  2. Verify that Track 1 begins with SKC2 and is exactly 70 characters.
  3. Verify that Track 2 begins with 02, contains only decimal digits, and is exactly 34 digits.
  4. Verify that Track 3 contains only decimal digits and is exactly 97 digits.
  5. Base45-decode Track 1 after the magic to exactly 44 bytes.
  6. Decode the next 17 Track 2 digits to exactly 7 bytes.
  7. Decode Track 3 to exactly 40 bytes.
  8. Concatenate the three byte parts in track order and require exactly 91 bytes.
  9. Decode the last 15 Track 2 digits to six bytes and compare them with the first six bytes of SHA-256(payload).
  10. Verify the NIP-49 version and field structure.
  11. Prompt for the password, normalize it to NFKC, and decrypt according to NIP-49.
  12. Verify that the decrypted value is a valid secp256k1 secret scalar before using it.

The transport checksum detects read errors and tracks taken from different cards. It is not an authentication mechanism. NIP-49's Poly1305 tag provides authentication during decryption. A decryption failure may indicate an incorrect password, damaged data, or tampering.

Write verification and media errors

Magnetic stripes are not lossless storage. A correctly written and verified card should reproduce the exact track data, but dirty or misaligned heads, incorrect swipe speed, coercivity mismatch, wear, scratches, bending, heat, magnets, or reader firmware can cause failed, partial, or corrupted reads and writes.

Character parity and each track's LRC detect many physical errors, but they do not detect every possible multi-bit error. Writers therefore MUST perform an immediate read-after-write and validate the complete reconstructed value. Writers SHOULD require at least two consecutive successful reads before issuing a card.

On a read failure, software SHOULD prompt for another swipe. After three consecutive failures, it SHOULD abort the current attempt rather than permanently declaring the card invalid. An SKC card SHOULD NOT be the only backup of a secret key or ncryptsec value.

Software readers

Keyboard-wedge readers send decoded track data as keyboard input. Applications MUST confirm that a reader exposes every track required by the detected format and MUST account for any device-specific separators or sentinels.

HID and serial readers expose track buffers through their SDK or device protocol. Applications using those modes apply the same decoding and validation procedures after removing framing supplied by the hardware.

Applications MUST treat reader input, passwords, derived keys, and decrypted keys as secret material. They SHOULD prevent these values from appearing in text fields, logs, crash reports, clipboard history, shell history, or analytics, and SHOULD erase temporary buffers when practical.

Security considerations

An SKC1 card is a bearer instrument. Anyone who can swipe or copy it can learn and use the secret key. Its checksum detects errors but provides no protection against disclosure, copying, or intentional modification.

An SKC2 card contains a NIP-49 encrypted key and is subject to offline password guessing if stolen. Users SHOULD choose a strong password and an appropriate NIP-49 LOG_N value. A short PIN alone does not provide adequate protection against an offline attack.

Keyboard-wedge readers can send secrets to whichever application currently has focus. Implementations SHOULD use a dedicated input screen and disable or avoid input recording where possible.

Future incompatible formats MUST use a different four-character Track 1 magic value so readers can distinguish them at swipe time.

Discussion

Connect a key to comment.