# User Wallet Data Format Encryption Extension

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This specification defines the canonical encrypted wrapper for [BRC-38](/outpoints/0038.md) 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](/wallet/0002.md)
* a portable, versioned binary envelope in the spirit of [BRC-78](/peer-to-peer/0078.md)
* 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, but they 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`, and reserved bytes
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 zero
* `Nonce Length` is zero
* reserved bytes are non-zero
* any Argon2id parameter is invalid, including:
  * iterations is zero
  * memoryKiB is zero
  * parallelism is zero
  * hashLength is not `32`
* AES-GCM authentication fails
* the decrypted payload is not valid UTF-8 JSON
* the decrypted payload is not a valid BRC-38 document

### 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

## 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

* [BRC-2: Data Encryption and Decryption](/wallet/0002.md)
* [BRC-38: User Wallet Data Format](/outpoints/0038.md)
* [BRC-40: User Wallet Data Synchronization](/outpoints/0040.md)
* [BRC-78: Serialization Format for Portable Encrypted Messages](/peer-to-peer/0078.md)
* [Argon2](https://www.rfc-editor.org/rfc/rfc9106)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bsv.brc.dev/outpoints/0039.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
