BRC-39 Outpoints

User Wallet Data Format Encryption Extension

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

Ty Everett (ty@projectbabbage.com)

How it connects

Relationships

Authoritative content mirror

Full upstream text

View source ↗
Rendered from outpoints/0039.md at the indexed upstream commit

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 envelope 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 Wallet Toolbox 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 nonce 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:

Field Length Description
Magic 4 bytes ASCII WDAT
Format Version 1 byte Currently 0x01
Protector Type 1 byte Currently 0x01 for password-protected export
Inner Format 1 byte Currently 0x26 (decimal 38) for BRC-38
KDF Type 1 byte Currently 0x01 = Argon2id
Flags 1 byte Currently 0x00; all bits reserved and MUST be zero
Salt Length 1 byte Currently 0x20 (32)
Nonce Length 1 byte Currently 0x20 (32)
Argon2id Iterations 4 bytes Work factor
Argon2id Memory KiB 4 bytes Memory cost
Argon2id Parallelism 1 byte Parallelism
Argon2id Hash Length 1 byte Derived key length; currently 32
Reserved 12 bytes MUST be all zero
Salt variable Salt Length bytes
Nonce variable Nonce Length bytes
CiphertextAndTag variable AES-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-42/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

Item Value
Password Café wallet export
Password, NFC, UTF-8 436166c3a92077616c6c6574206578706f7274
Password, NFD, UTF-8 43616665cc812077616c6c6574206578706f7274, which normalizes to the NFC bytes and so derives the same key
Salt (32 bytes) 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Nonce (32 bytes) 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
Argon2id iterations 7, memoryKiB 131072, parallelism 1, hashLength 32
Derived key bf0d243b3ac3d65758c7b9e133431947463df931b3574a2dc8eb3ea70e42bc55
Header (33 bytes) 574441540101260100202000000007000200000120000000000000000000000000
First 16 bytes of ciphertext dc61aed5af05d98600370dfdb1bffeb3
Authentication tag 7d5ad6d1fe8723ed510de2dc84f69a57
File 987 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.

Edit Offset New value Refused
magic WDAX 3 0x58 before key derivation
format version 2 4 0x02 before key derivation
protector type 2 5 0x02 before key derivation
inner format 39 6 0x27 before key derivation
KDF type 2 7 0x02 before key derivation
flags 0x01 8 0x01 before key derivation
salt length 0 9 0x00 before key derivation
nonce length 0 10 0x00 before key derivation
iterations 0 11-14 00000000 before key derivation
memoryKiB 0 15-18 00000000 before key derivation
parallelism 0 19 0x00 before key derivation
hash length 31 20 0x1f before key derivation
a reserved byte 0x01 32 0x01 before key derivation
salt length 7 9 0x07 before key derivation
memoryKiB 31 with parallelism 4 15-19 0000001f04 before key derivation
iterations 15 11-14 0000000f before key derivation (Section 11.1)
memoryKiB 262145 15-18 00040001 before key derivation (Section 11.1)
parallelism 5 19 0x05 before key derivation (Section 11.1)
iterations 4294967295 11-14 ffffffff before key derivation (Section 11.1)
memoryKiB 4294967295 15-18 ffffffff before key derivation (Section 11.1)
iterations 8 11-14 00000008 after key derivation: authentication fails
the first ciphertext byte 97 its value XOR 0x01 after key derivation: authentication fails
the last tag byte 986 its value XOR 0x01 after 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