NIPs by PolleramaCommunity NIPs, surfaced by trustConnect
npub1ye5ptcxfyyx...

Schnorr Key Card Formats

Published Oct 6, 2026

SKC: Schnorr Key Card Formats

draft optional

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

  • SKC1 stores a raw secret key on Track 1. The card is a bearer instrument.
  • SKC2 stores the exact 91-byte encrypted-key payload defined by NIP-49 across all three tracks.
  • SKC3 stores an unencrypted NIP-46 bunker connection across all three tracks.

SKC1 and SKC2 protect the raw 32-byte secp256k1 secret scalar represented by an nsec, not the Bech32-encoded nsec text. SKC3 holds a remote signer public key, a client secret key, and relay addresses. It does not contain the remote signer's secret key.

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. Writers SHOULD leave Tracks 2 and 3 blank; readers MUST accept and preserve existing auxiliary-track data because SKC1 derives and verifies its value from Track 1 alone. Writers SHOULD offer explicit auxiliary-track erasure when blank tracks are required.

Layout

TrackFieldLengthContent
1Magic and version4SKC1
1Secret key64Raw 32-byte secret key as uppercase hexadecimal
1Checksum8First 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 when possible; erase existing auxiliary data only when explicitly requested.

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:

``text n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 ``

  1. Compare the checksum with the first four bytes of SHA-256(sk).
  2. Accept the key only if every check succeeds. Ignore Tracks 2 and 3 for key validation.

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
TrackFieldLengthContent
1Magic and version4SKC2
1Part A66Base45 encoding of part_a
2Version marker202
2Part B17Fixed-width decimal encoding of part_b
2Transport checksum15Fixed-width decimal encoding of the first six bytes of SHA-256(payload)
3Part C97Fixed-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 and SKC3 use 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. For each pair a, b, calculate:

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

Emit 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. Decoders MUST reject values greater than or equal to 2^(8 * byte_length):

FieldBytesDecimal digits
SKC2 Part B717
SKC2 transport checksum615
SKC2 Part C4097
SKC3 Part B820
SKC3 transport checksum615
SKC3 Part C43104

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 ASCII decimal digits, and is exactly 34 digits.
  4. Verify that Track 3 contains only ASCII 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.

SKC3: NIP-46 bunker connection

SKC3 stores the remote signer's 32-byte x-only public key, a 32-byte client secret scalar, and one or more relay URLs. These fields correspond to the public key, local key, and relays in [nbunksec](https://github.com/sandwichfarm/encoded-entities/blob/master/src/encoders/nbunksec.ts). SKC3 does not store an optional nbunksec connect secret. A recovered card can produce an nbunksec without that field; a connection that requires the omitted secret needs it from another source.

SKC3 requires all three tracks. Its contents are unencrypted and grant anyone who can copy them the client identity used to request signatures from the bunker.

Payload layout

The payload is exactly 99 bytes:

OffsetLengthContent
032Remote signer x-only public key
3232Client secret key, unsigned big-endian secp256k1 scalar
6435Relay entries followed by zero padding

Each relay entry consists of a one-byte header followed by the relay address without its scheme. The low seven header bits give the address length in bytes (1 through 127); the high bit is 0 for wss:// and 1 for ws://. Addresses MUST use printable ASCII bytes 0x21 through 0x7E and MUST contain no spaces. At least one relay entry is required. The complete sequence of entries MUST fit in 35 bytes; unused bytes MUST be zero. A zero header starts padding, and a decoder MUST reject any nonzero byte after it. Relay order is preserved.

The public key MUST be an x-coordinate on secp256k1 with 0 < x < p, where p = 2^256 - 2^32 - 977. The client secret MUST satisfy 0 < secret < n using the group order given under SKC1.

Track layout

Split the 99-byte payload without changing byte order:

part_a = payload[0:48]   # 48 bytes
part_b = payload[48:56]  # 8 bytes
part_c = payload[56:99]  # 43 bytes
TrackFieldLengthContent
1Magic and version4SKC3
1Part A72Base45 encoding of part_a
2Version marker203
2Part B20Fixed-width decimal encoding of part_b
2Transport checksum15Fixed-width decimal encoding of the first six bytes of SHA-256(payload)
3Part C104Fixed-width decimal encoding of part_c

The logical track lengths are 76 characters, 37 digits, and 104 digits. Base45 and decimal encoding follow the rules defined under SKC2.

Test vector

This vector uses the publicly known secret key from SKC1 as its client key. It MUST NOT be used for a real bunker connection.

Remote signer public key: 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
Client secret key:        67DEA2ED018072D675F5415ECFAED7D2597555E202D85B3D65EA4E58D2D92FFA
Relays:                   wss://relay.nsec.app, wss://relay.damus.io

Track 1: SKC3QHF3@CJQVTWN5*A*KC/4QXH1*[email protected]_2.5D-QKO80DNE2/E-B8LBQZCR
Track 2: 0306446152870849436477050782028621640
Track 3: 14266510200503632746400788170566837955622720621173563866314328341639055799890345213167088642057436135424

Writing

  1. Obtain the remote signer public key, client secret key, and at least one relay. If importing nbunksec, discard its optional connect secret for this format and inform the user.
  2. Validate both keys and encode the relay entries into the 35-byte area with zero padding.
  3. Split the resulting 99-byte payload, encode each part, and compute the six-byte transport checksum.
  4. Write all three tracks and read the card back. Complete the full SKC3 validation procedure before issuing it.

Reading

  1. Require all three tracks. Track 1 MUST start with SKC3 and contain exactly 76 characters; Track 2 MUST start with 03 and contain exactly 37 ASCII digits; Track 3 MUST contain exactly 104 ASCII digits.
  2. Base45-decode Track 1 after the magic to 48 bytes. Decode the 20-digit Track 2 field to eight bytes and Track 3 to 43 bytes, rejecting out-of-range decimal values.
  3. Concatenate the parts to exactly 99 bytes. Decode the last 15 Track 2 digits to six bytes and compare them with the first six bytes of SHA-256(payload).
  4. Validate the remote signer public key, client secret scalar, relay entries, and zero padding before using the connection.

The checksum detects accidental corruption and mismatched tracks. It does not authenticate the payload.

Write verification and media errors

Magnetic stripes are not lossless storage. 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 not 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, ncryptsec, or bunker connection.

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 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 hardware framing.

Applications MUST treat reader input, passwords, derived keys, decrypted keys, and SKC3 connection data 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.

An SKC3 card is unencrypted. Anyone who can swipe or copy it can impersonate its client identity and request signatures from the remote signer, subject to any controls enforced by that signer. Its checksum is not authentication. Revoke or disable a compromised client key at the signer. An SKC3 card does not back up the remote signer's private key. An optional nbunksec connect secret is omitted, so callers requiring it must obtain it separately.

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.