Beersy
BRC-39

User Wallet Data Format Encryption Extension

A file containing everything about your wallet is not something to leave lying around unprotected. This defines the encrypted wrapper around such an export, so a backup can be stored safely and restored with a password.

About Backup and recovery, getting your keys back after losing the device.

AlsoEncryption

Filed under Outpoints, though most standards about backup and recovery sit in Wallet.

Ty Everettchanged 20 Oct 202611 min read

Reference for an AI

Everything an assistant needs to answer questions about BRC-39 accurately, including what it depends on.

Summary

Why
Wallet data needed a single, safe, password-protected file that users could download, store offline, and later restore into any conforming wallet.
What
BRC-39 is a versioned binary file format that encrypts a BRC-38 wallet export with a password-derived AES-256-GCM key so it can be safely backed up and restored.
How
An exporter serializes a BRC-38 document, derives a 32-byte key from the user's NFC-normalized password using Argon2id, encrypts the JSON with AES-256-GCM, and writes a versioned binary header plus salt, , ciphertext and tag as wallet.brc39, which an importer parses, validates against strict bounds, and decrypts…

What this lets you do

  • Export a full wallet (BRC-38 data) into one password-protected binary file
  • Derive a strong encryption key from a user password with Argon2id
  • Decrypt and validate an imported wallet file before trusting its contents
  • Reject malformed or suspiciously-expensive files before doing any key derivation
  • Move a wallet between different wallet implementations or storage providers

Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.

wallet.brc39 eheader fieldsBRC-38 JSON pl

The specification

Abstract

This specification defines the canonical encrypted wrapper for BRC-38 user wallet data exports.

It standardizes a downloadable binary file format, conventionally named wallet.brc39, for password-protected wallet export and import. The design is intended for strong user data portability, backups, archival export, GDPR-style data access, and migration between wallet implementations and storage providers.

BRC-39 uses:

  • the BRC-38 payload as its canonical plaintext inner document
  • the AES-256-GCM symmetric encryption primitive used by BRC-2
  • a portable, versioned binary in the spirit of BRC-78
  • Argon2id as the mandatory password key-derivation function for new exports

Motivation

BRC-38 defines what wallet data must be portable. It does not define how that data should be safely delivered to a user as a single downloadable file.

For real-world portability, the default export artifact must be:

  • encrypted
  • password protected
  • self-describing
  • suitable for offline storage
  • stable enough to move between implementations

This document defines that artifact.

Specification

1. Scope

This specification defines:

  • the canonical encrypted file format for a single-user BRC-38 export
  • password normalization rules
  • key derivation rules
  • authenticated encryption rules
  • file parsing, validation, export, and import behavior

It does not define:

  • secret-sharing or recovery-key workflows
  • multi-file backup sets
  • multi-user containers
  • wallet-runtime key snapshots unrelated to BRC-38

2. Conformance

An implementation conforms to BRC-39 if it can:

  1. serialize a valid BRC-38 payload
  2. encrypt that payload into a valid BRC-39 file
  3. decrypt and validate a valid BRC-39 file
  4. recover the original BRC-38 payload without loss

3. Canonical Export Artifact

The canonical encrypted export artifact:

  • MUST be a binary file
  • SHOULD be named wallet.brc39
  • SHOULD use media type application/vnd.brc39.wallet

The inner plaintext payload MUST be a complete BRC-38 document serialized as canonical UTF-8 JSON.

4. Cryptographic Profile

4.1 Plaintext

The plaintext to be encrypted is:

  • the exact UTF-8 byte sequence of a valid canonical BRC-38 JSON document

4.2 Password Encoding

Before key derivation, the user password MUST be:

  1. interpreted as a Unicode string
  2. normalized to NFC
  3. encoded as UTF-8 bytes

No additional trimming, case folding, or whitespace normalization is permitted.

4.3 Key Derivation

New BRC-39 exports MUST use Argon2id.

The canonical derived-key length is:

  • 32 bytes

The canonical default Argon2id parameters are:

  • iterations: 7
  • memoryKiB: 131072
  • parallelism: 1
  • hashLength: 32
  • saltLength: 32

These defaults align with the Argon2id defaults this specification is intended to standardize.

Implementations MAY use stronger Argon2id parameters within the interoperability bounds of Section 11.1, and MUST encode the exact parameters used in the file header.

4.4 Authenticated Encryption

The content-encryption algorithm is AES-256-GCM.

The content-encryption key is the 32-byte Argon2id-derived key.

The file length is:

  • 32 bytes

The nonce MUST be generated randomly for each export.

The GCM authentication tag length MUST be 16 bytes.

4.5 Header Validation

Header fields are validated before decryption as specified below. BRC-39 encrypts only the BRC-38 plaintext payload.

5. File Layout

All integers in the binary file MUST be unsigned and encoded in big-endian byte order.

The canonical BRC-39 file layout is:

FieldLengthDescription
Magic4 bytesASCII WDAT
Format Version1 byteCurrently 0x01
Protector Type1 byteCurrently 0x01 for password-protected export
Inner Format1 byteCurrently 0x26 (decimal 38) for BRC-38
KDF Type1 byteCurrently 0x01 = Argon2id
Flags1 byteCurrently 0x00; all bits reserved and MUST be zero
Salt Length1 byteCurrently 0x20 (32)
Nonce Length1 byteCurrently 0x20 (32)
Argon2id Iterations4 bytes factor
Argon2id Memory KiB4 bytesMemory cost
Argon2id Parallelism1 byteParallelism
Argon2id Hash Length1 byteDerived key length; currently 32
Reserved12 bytesMUST be all zero
SaltvariableSalt Length bytes
NoncevariableNonce Length bytes
CiphertextAndTagvariableAES-256-GCM ciphertext followed by 16-byte tag

6. Header Field Requirements

6.1 Magic

The file MUST begin with the ASCII bytes:

57 44 41 54

which spell WDAT.

6.2 Format Version

The current format version is 1.

Files with unsupported versions MUST be rejected.

6.3 Protector Type

The following protector types are defined:

  • 0x01: password-based encryption

The following values are reserved for future extension:

  • 0x02 to 0xff

This version of BRC-39 standardizes only password-based encryption.

6.4 Inner Format

The Inner Format field identifies the plaintext payload.

For BRC-39 wallet exports:

  • Inner Format MUST be 38

Meaning the decrypted plaintext MUST be a valid BRC-38 document.

6.5 KDF Type

The following KDF type is defined:

  • 0x01: Argon2id

All other KDF type values are reserved for future specifications. This version of BRC-39 defines no alternate KDF import mode.

6.6 Flags

All flag bits are reserved for future specifications.

For this specification:

  • Flags MUST be 0x00

6.7 Reserved Bytes

All reserved bytes MUST be zero on export and MUST be ignored only if zero on import. Non-zero reserved bytes MUST cause rejection.

7. Export Procedure

To create a BRC-39 file, an exporter MUST:

  1. construct a valid BRC-38 document
  2. serialize it as canonical UTF-8 JSON
  3. normalize the user password to NFC and encode it as UTF-8
  4. generate a random 32-byte salt
  5. derive a 32-byte key using Argon2id and the encoded password
  6. generate a random 32-byte nonce
  7. construct the BRC-39 header
  8. encrypt the BRC-38 plaintext using AES-256-GCM with:
    • key = derived key
    • nonce = header nonce
  9. append CiphertextAndTag
  10. emit the resulting bytes as wallet.brc39

8. Import Procedure

To import a BRC-39 file, an importer MUST:

  1. parse and validate the binary header
  2. verify Magic, Format Version, Protector Type, Inner Format, KDF Type, Flags, reserved bytes, Salt Length, Nonce Length, and the Argon2id parameters, as Sections 11 and 11.1 require
  3. obtain the user password
  4. normalize the password to NFC and encode it as UTF-8
  5. derive the decryption key using the encoded KDF parameters from the file
  6. decrypt CiphertextAndTag with AES-256-GCM using:
    • key = derived key
    • nonce = header nonce
  7. reject the file if GCM authentication fails
  8. parse the plaintext as UTF-8 JSON
  9. validate that the plaintext is a valid BRC-38 document

Only after successful BRC-38 validation may the importer proceed to import wallet data into local storage.

9. KDF Compatibility

BRC-39 standardizes only Argon2id for password-based wallet export encryption.

Implementations MUST reject files whose KDF Type is not 0x01. Compatibility with pre-standard password-derived wallet internals is outside the BRC-39 file format.

10. Relationship to BRC-2 and BRC-78

10.1 BRC-2

BRC-39 uses the same symmetric encryption primitive family as BRC-2:

  • AES-256-GCM

Unlike BRC-2 application messages, BRC-39 stores the nonce explicitly in the binary file header instead of prepending it to a message payload.

10.2 BRC-78

BRC-39 follows the same general design philosophy as BRC-78:

  • binary, versioned, self-describing encrypted envelopes

BRC-78 is oriented toward portable encrypted inter-party messages using BRC-2//BRC-43 derivation. BRC-39 instead targets password-protected at-rest wallet export. This version does not standardize recipient-encrypted wallet exports, but future protector types MAY define such a mode.

11. Validation Rules

A BRC-39 file MUST be rejected if:

  • Magic is not WDAT
  • Format Version is unsupported
  • Protector Type is unsupported
  • Inner Format is not 38
  • KDF Type is not 0x01
  • Flags is not 0x00
  • Salt Length is less than 8 (the Argon2 reference implementation refuses a shorter salt)
  • Nonce Length is zero
  • reserved bytes are non-zero
  • any Argon2id parameter is invalid, including:
    • iterations is zero
    • memoryKiB is zero
    • parallelism is zero
    • memoryKiB is less than 8 times parallelism (RFC 9106, Section 3.1)
    • hashLength is not 32
  • any Argon2id parameter is outside the interoperability bounds of Section 11.1
  • AES-GCM authentication fails
  • the decrypted payload is not valid UTF-8 JSON
  • the decrypted payload is not a valid BRC-38 document

11.1 Resource Limits

An importer has to derive the key before it can check the authentication tag, so the Argon2id parameters in the header decide how much memory and time it spends on a file before it can tell whether the file is genuine. Without a limit, a file of under a hundred bytes can ask for almost 4 TiB of memory or more than four billion passes, and a file written with stronger parameters than another importer supports does not open there.

The interoperability bounds are: iterations at most 14, memoryKiB at most 262144 (256 MiB), and parallelism at most 4. The canonical defaults of Section 4.3 are within them.

  • An exporter MUST NOT write parameters outside the interoperability bounds.
  • An importer MUST refuse a file whose parameters are outside the interoperability bounds, before deriving any key.
  • An importer MUST NOT refuse a file because of parameters within the interoperability bounds.

12. Privacy and Operational Requirements

BRC-39 files contain the complete user wallet export, protected only by the chosen password and KDF parameters.

Wallet implementations:

  • MUST warn users that weak passwords materially weaken the file
  • SHOULD encourage long passwords or passphrases
  • SHOULD avoid embedding password hints in the file
  • SHOULD default to Argon2id parameters at least as strong as the canonical defaults

13. Canonical Example Header Semantics

An example canonical new-export header therefore means:

  • file type: wallet.brc39
  • payload type: BRC-38
  • protection mode: password
  • KDF: Argon2id
  • salt: 32 random bytes
  • nonce: 32 random bytes
  • plaintext: canonical UTF-8 BRC-38 JSON

14. Test Vectors

All byte strings are hexadecimal. The plaintext of both positive vectors is this canonical BRC-38 document (874 bytes of UTF-8, SHA-256 e76c1eb7a57cdf87e8d8a9c50b783e50decffb54c586bb86fa63186d7a31e48c), a synthetic export with placeholder keys:

{"brc":38,"exportedAt":"2026-01-02T03:04:05.006Z","formatVersion":1,"sourceStorage":{"chain":"test","created_at":"2026-01-02T03:04:05.006Z","dbtype":"SQLite","maxOutputScript":1024,"storageIdentityKey":"021111111111111111111111111111111111111111111111111111111111111111","storageName":"synthetic","updated_at":"2026-01-02T03:04:05.006Z"},"tables":{"certificateFields":[],"certificates":[],"commissions":[],"outputBaskets":[],"outputTagMaps":[],"outputTags":[],"outputs":[],"provenTxReqs":[],"provenTxs":[],"syncStates":[],"transactions":[],"txLabelMaps":[],"txLabels":[]},"title":"User Wallet Data Format","user":{"activeStorage":"021111111111111111111111111111111111111111111111111111111111111111","created_at":"2026-01-02T03:04:05.006Z","identityKey":"032222222222222222222222222222222222222222222222222222222222222222","updated_at":"2026-01-02T03:04:05.006Z","userId":1}}

14.1 Vector 1: canonical parameters, fixed salt and nonce

ItemValue
PasswordCafé wallet export
Password, NFC, UTF-8436166c3a92077616c6c6574206578706f7274
Password, NFD, UTF-843616665cc812077616c6c6574206578706f7274, which normalizes to the NFC bytes and so derives the same key
Salt (32 bytes)000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Nonce (32 bytes)202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
Argon2iditerations 7, memoryKiB 131072, parallelism 1, hashLength 32
Derived keybf0d243b3ac3d65758c7b9e133431947463df931b3574a2dc8eb3ea70e42bc55
Header (33 bytes)574441540101260100202000000007000200000120000000000000000000000000
First 16 bytes of ciphertextdc61aed5af05d98600370dfdb1bffeb3
Authentication tag7d5ad6d1fe8723ed510de2dc84f69a57
File987 bytes (header, salt, nonce, ciphertext, tag), SHA-256 90e7c4cb45ae17b529374a3b8c86f7147cdbbf5af5efe4fa9b1d5fda8b84a30a

An exporter given these inputs writes exactly this file, and an importer given either form of the password opens it to the document above.

14.2 Vector 2: a file written by Wallet Toolbox

Wallet Toolbox 2.14.4 wrote this file with its own random salt and nonce, the canonical parameters, and the password Café synthetic pw (UTF-8 436166c3a92073796e746865746963207077). It opens to the same document. 987 bytes, SHA-256 6343ecc88ba3f38228e419fa1803be78a178e13f0f059537b395d9de472f6e6f:

574441540101260100202000000007000200000120000000000000000000000000a1ccb54521ec73a2f4fea5c40fb288
aa614ccdc45e783c5bda609f7c46fb6d6476ce7771b08242281f9da710ecf6d1c8a942c3e754a3d42e8d2082e597768c
5c3fc684dacb78388c339b2621fddd7cbeeb7ad00be25d57cc520ce717f8ea661fdbd29199509f5e625826f03158543d
5fcb7f86eac816e639b51bce84cec760789cba75c3cc6a0b0fd210621f45dca818c39ebdac56f3016c06869ebe08dc0a
2cbdeb39c47139c38a335fff9f9c1c52d19c8a4d5d477a0f9321a4069cd0ca5621d3e14d9653dd5710919ff864806be3
6723fd39ba833137315a41bd87eec1c895d4f805b2f5077ea2598557a0d9c41e68a978ed5263f53efaf0146cca9db5db
79a4e0e14ce6d48581e16623c87000e02b3c59086053ec8544251728138cb6c283f2910945eabbc66a91358f9a4534e8
59c09c04f41e84180580c9555b47e05fde7d3ffe862a3ce558e7dca53e29517387ad7342d4fee7f12553cc5251cfa7ff
e4faabe28234d8bb137dfed71dfbdd413c72aa66df2d7cbb378cb626aeb545ce2f070865d2b6f07784cc70dc9875fbb5
b05bbd6a52f47561c9e83d26180a4a04d127fe732a5343547037be749141dd77dde9b758ab66af54871831078f56e2bb
dce84856a012a8f597afe3a94991ff5ce2c87770773f6438e71fa12995d30dc8b6b5eedbb7c051ac042bf496e4f4bbf7
569cd0ff770aa6f25e84a60f348cfe953c4ddaef48128ec961b288c170a1b23d87f0223f1bbe9997fecf6ea56e7380c2
d337fce2d8ade729170b42473c0d2d61d2e54ca50306b00e3d3480cdb0bda2676c89855c92ecda20e51347108483e74c
b48901074cb36fabf31c34087adce028f640bcf22c9ff0c7d19bc6c27e3d7bbbb99ad1ff963b2d01d86aa6a7fbc504fa
e330abc0753097f401e29a1b430f55508b35a4908ab419d84407eaa1630971a3ab98833732e5f8e624f36ce9f9960aa6
8e765a8d851906c6ff3363f0aa8963f1b96aeae7de34b044098f35a08325d420e071b660bc9defb0b10986477dd13d3f
436c86bf7df5032465a8232047e140dc1c227069217a377cf59ab94f0dbbecaebbd94df6db6efee510d39da425d03f16
0b9fe81699d882ec19ac5ea657e6dcbbc843ec04642c367a3264bf2ed5be606a914c2d99552c42d8672b65d8eca2dd15
d16ccc8963fc48aaf9a4412c88f453ab05adb37ae3b8b5cd23fb1f25106ab5e034859c934958610c13b391ea6c861f0d
bd194ffbe71022cd93742a07827e497dfb2cb7b9126078e73364344b74a11daed477d9a8d1f07b31bd43fe989066e005
69b71afedda4fd5f4e4aee6ebbec64988ea9aeb510c617ea5b26e2

14.3 Files an importer must refuse

Each row edits Vector 1 at the byte offset given (0 is the first byte of the file) and is tried with Vector 1's password. An importer refuses a header that breaks Section 11 or 11.1 before it derives any key, so it never does the work such a header asks for. A valid header with another work factor derives another key, so authentication fails, as it does for an edited ciphertext or tag.

EditOffsetNew valueRefused
magic WDAX30x58before key derivation
format version 240x02before key derivation
protector type 250x02before key derivation
inner format 3960x27before key derivation
KDF type 270x02before key derivation
flags 0x0180x01before key derivation
salt length 090x00before key derivation
nonce length 0100x00before key derivation
iterations 011-1400000000before key derivation
memoryKiB 015-1800000000before key derivation
parallelism 0190x00before key derivation
hash length 31200x1fbefore key derivation
a reserved byte 0x01320x01before key derivation
salt length 790x07before key derivation
memoryKiB 31 with parallelism 415-190000001f04before key derivation
iterations 1511-140000000fbefore key derivation (Section 11.1)
memoryKiB 26214515-1800040001before key derivation (Section 11.1)
parallelism 5190x05before key derivation (Section 11.1)
iterations 429496729511-14ffffffffbefore key derivation (Section 11.1)
memoryKiB 429496729515-18ffffffffbefore key derivation (Section 11.1)
iterations 811-1400000008after key derivation: authentication fails
the first ciphertext byte97its value XOR 0x01after key derivation: authentication fails
the last tag byte986its value XOR 0x01after key derivation: authentication fails

Implementation

An implementation based on Wallet Toolbox can produce a BRC-39 file by:

  1. exporting a full BRC-38 payload for the selected user
  2. deriving a password key with Argon2id using the standardized Toolbox-compatible default parameters
  3. encrypting the payload with AES-256-GCM
  4. writing the standardized binary envelope described above

References

Was this helpful?

Search Beersy

Search standards by number, title, author or topic