bip-85: add example use for age key derivation #2174

pull dmonakhov wants to merge 1 commits into bitcoin:master from dmonakhov:bip-85-age-example changing 1 files +36 −0
  1. dmonakhov commented at 5:06 PM on May 23, 2026: none

    Motivation

    age is a widely-deployed file-encryption format. Both its classic (X25519, since 2021) and post-quantum (X-Wing / HPKE-MLKEM768-X25519, age v1.3.0+ Dec 2024) identity types take a 32-byte uniform seed as the secret-key material — which is precisely what BIP-85's HEX application at num_bytes=32 already returns.

    This PR adds a brief example-use subsection at the end of the HEX application section, documenting how to interpret the existing 32-byte HEX output as an age identity. No new application number, no normative MUSTs added, no changes required in existing implementations — the HEX application already produces exactly what age consumes; this PR just records the connection in the spec.

    Documenting this gives users a single backup story: the same BIP-39 mnemonic that already secures their Bitcoin wallets can deterministically produce age identities, with no additional key-rotation infrastructure on top of what they already do.

    It also unblocks downstream hardware-wallet firmware integration maintainers of firmware projects accept "implement this BIP-85 use case" much more readily than "implement a convention from a third-party repo." A spec-blessed convention turns the conversation from a bespoke contribution into a standards-conformance issue.

    What changes

    A new ====<code>age</code> file encryption keys==== subsection at the end of the ===HEX=== section in bip-0085.mediawiki.

    The subsection:

    • Describes the bech32 encoding for both classic and PQ age identities + their corresponding recipients.
    • Adds a worked test vector using the same master xprv as the existing HEX test vector, so a reader can derive both side-by-side from one master.

    Scope

    This amendment is intentionally minimal:

    • No new application number — reuses the existing HEX application (128169').
    • No normative MUSTs added beyond what BIP-85 already says about the HEX application.
    • No changes required in existing implementations (bipsea, bip85-js, ethankosakovsky/bip85).

    Possible follow-ups (not in this PR):

    • A dedicated AGE subsection paralleling the existing RSA-GPG subsection, with full worked examples for each flavour and an optional index-partition recommendation (e.g. [0, 1000) for PQ identities, [1000, 2000) for classic identities, to avoid producing the same 32 bytes under two different HRPs when the same master is used for both flavours).
    • A dedicated application number for age (e.g. 0x616765' = hex-pack of ASCII "age"), with independent paths per flavour.

    These are noted only to clarify that this PR deliberately picks the minimal form; they can be raised separately if there is appetite.

    Implementations

    Per review, the spec text does not link a reference implementation. A follow-up PR extending bipsea with age derivation is planned.

    Open question

    Should the subsection also include the index-range partition guidance ([0, 1000) for PQ identities, [1000, 2000) for classic identities) to avoid producing the same 32 bytes under two different HRPs when both flavours are derived from one master? Including it would be useful guidance; excluding it keeps the amendment maximally minimal. Open to reviewer preference.


    cc @ethankosakovsky @akarve — original BIP-85 authors per the BIP header. Happy to revise based on review feedback.

  2. kwest3170-wq commented at 1:00 AM on May 24, 2026: none

    Thanks i apologies

  3. in bip-0085.mediawiki:287 in 6cb352dd26
     281 | @@ -282,6 +282,21 @@ INPUT:
     282 |  OUTPUT
     283 |  * DERIVED ENTROPY=492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c
     284 |  
     285 | +====Example use: age key derivation====
     286 | +
     287 | +The 32-byte output at <code>m/83696968'/128169'/32'/{index}'</code> is uniformly random and is suitable as the private-key seed for the [https://age-encryption.org/v1 age] file-encryption format. For classic age identities, bech32-encode the 32 bytes with HRP <code>AGE-SECRET-KEY-</code>; the corresponding recipient is the bech32 encoding (HRP <code>age</code>) of <code>X25519(seed, BASEPOINT)</code>. For post-quantum age identities (age v1.3.0+, Dec 2024), the same 32 bytes are a valid X-Wing seed; bech32-encode with HRP <code>AGE-SECRET-KEY-PQ-</code>. X-Wing internally SHAKE256-expands the seed into ML-KEM-768 and X25519 components, and the recipient is the bech32 encoding (HRP <code>age1pq</code>) of the X-Wing encapsulation key. A reference implementation is [https://github.com/dmonakhov/age-keygen-det age-keygen-det].
    


    akarve commented at 9:13 PM on May 24, 2026:

    A few requests for this section:

    1. Please insert newlines every ~80-90 chars so this file reads well in a terminal
    2. The Dec 2024 date is probably TMI, version makes sense but dates generally don't age well
    3. You say "uniformly random" but the point is "cryptographically random"
    4. Consider breaking most of the above into a small table for readability and using standard nomenclature like || for concat, etc.

    Example: please check the math and semantics (assume what's here is wrong), but to give you the idea:

    flavor role HRP bytes encoded in data encoded string
    classic identity AGE-SECRET-KEY- seed (32 B) AGE-SECRET-KEY- || 1 || AG7WK…UKUMWQ || WNNT4U
    classic recipient age X25519(seed, G) (32 B) age || 1 || m0hhzx…256wslq || mdz8e9
    PQ (X-Wing) identity AGE-SECRET-KEY-PQ- seed (32 B, same bytes) AGE-SECRET-KEY-PQ- || 1 || AG7WK…UKUMWQ || 5AN2M5
    PQ (X-Wing) recipient age1pq XWing.pk(seed) (≈1216 B) age1pq || 1 || (~1950 chars) || (6 chars)

    And then define your terms below, G, etc.


    akarve commented at 9:29 PM on May 24, 2026:

    "The 32-byte output at...": let's just say "HEX application outputs can be extended with concatenation to function as age file encryption keys" or something like that.


    dmonakhov commented at 7:44 AM on June 2, 2026:

    Please insert newlines every ~80-90 chars so this file reads well in a terminal The Dec 2024 date is probably TMI, version makes sense but dates generally don't age well You say "uniformly random" but the point is "cryptographically random"

    Ack. fixed with new version.

    please check the math and semantics (assume what's here is wrong),

    Oh.. thank you very much for pointing this. Indeed original test vectors was not correct, shame on me. Please see updated version. test vectors are now correct.


    dmonakhov commented at 7:46 AM on June 2, 2026:

    Ack.

  4. in bip-0085.mediawiki:293 in 6cb352dd26
     288 | +
     289 | +INPUT:
     290 | +* MASTER BIP32 ROOT KEY: xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
     291 | +* PATH: m/83696968'/128169'/32'/0'
     292 | +
     293 | +OUTPUT
    


    akarve commented at 9:22 PM on May 24, 2026:
    OUTPUT:
    
  5. jonatack added the label Proposed BIP modification on May 24, 2026
  6. jonatack added the label Pending acceptance on May 24, 2026
  7. in bip-0085.mediawiki:285 in 6cb352dd26
     281 | @@ -282,6 +282,21 @@ INPUT:
     282 |  OUTPUT
     283 |  * DERIVED ENTROPY=492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c
     284 |  
     285 | +====Example use: age key derivation====
    


    akarve commented at 9:25 PM on May 24, 2026:
    ====<code>age</code> file encryption keys====
    

    jonatack commented at 9:27 PM on May 24, 2026:

    It might be helpful to specify that "age" as used here specifically refers to a file encryption format, either in parentheses or with a footnote.


    dmonakhov commented at 7:45 AM on June 2, 2026:

    ACK. fixed

  8. jonatack added the label PR Author action required on May 25, 2026
  9. dmonakhov force-pushed on Jun 2, 2026
  10. jonatack removed the label PR Author action required on Jun 2, 2026
  11. in bip-0085.mediawiki:321 in 943f1d9d8f
     316 | +* DERIVED AGE IDENTITY (classic)=AGE-SECRET-KEY-1AG7WKZCZA689SAMECCL5K7E6Y854PGSN78K98J4KPRGNAPUKUMWQWNNT4U
     317 | +* DERIVED AGE RECIPIENT (classic)=age1m0hhzxelxsxnxm4ennvdpk75j8s7mn5w4tt3e4ntug5qx256wslqmdz8e9
     318 | +* DERIVED AGE IDENTITY (PQ)=AGE-SECRET-KEY-PQ-1AG7WKZCZA689SAMECCL5K7E6Y854PGSN78K98J4KPRGNAPUKUMWQ5AN2M5
     319 | +* DERIVED AGE RECIPIENT (PQ) SHA-256 (of the 1959-char bech32 string, no trailing newline)=855bd04ee0cd6cfdf5717fb946d859824a79fbbdf6304dadcc11dbb91abe0df6
     320 | +
     321 | +A reference implementation is [https://github.com/dmonakhov/age-keygen-det age-keygen-det].
    


    akarve commented at 1:09 AM on July 19, 2026:

    Can't have new reference implementations, esp. not in a subsection :) Please extend https://github.com/akarve/bipsea


    dmonakhov commented at 4:22 PM on July 29, 2026:

    Ack. bipsea is indeed the right place for reference implementation . Please see PR updated, Changes:

    • remove test vectors from PR description.
    • Removed the reference-implementation link from the subsection.
    • Follow-up PR adding age derivation to bipsea: akarve/bipsea#72.
  12. akarve commented at 1:10 AM on July 19, 2026: contributor

    Mostly looks good. Left one and line comment. For the PR description please delete the test vectors as they might have diverged and distract from the PR contents.

  13. murchandamus added the label PR Author action required on Jul 20, 2026
  14. bip-85: add example use for age key derivation
    Documents the connection between BIP-85's HEX application output
    at num_bytes=32 and the private-key seed format used by the age
    file-encryption format (https://age-encryption.org/v1) for both
    the classic X25519 identity (since age v1.0) and the
    post-quantum X-Wing identity (age v1.3.0+, Dec 2024).
    
    No new application number, no normative MUSTs added, no changes
    required in existing implementations. The amendment is purely
    documentational: a single subsection appended at the end of the
    HEX section.
    
    A test vector is added using the same master xprv as the
    existing HEX test vector in the same section, with both classic
    and post-quantum outputs. The PQ recipient (a ~1959-character
    bech32 string encoding the 1216-byte X-Wing public key) is
    included as a SHA-256 hash inline; the full string is in the
    reference implementation's test data.
    
    Reference implementation: https://github.com/dmonakhov/age-keygen-det
    - single-binary Go tool, BSD-3-Clause
    - cross-validates byte-for-byte against stock age-keygen -y in
      CI for both flavours
    5055fa2228
  15. dmonakhov force-pushed on Jul 29, 2026
  16. dmonakhov commented at 4:24 PM on July 29, 2026: none

    Mostly looks good. Left one and line comment. For the PR description please delete the test vectors as they might have diverged and distract from the PR contents.

    Done. please see PR updated.

  17. in bip-0085.mediawiki:299 in 5055fa2228
     294 | +|-
     295 | +| classic (X25519) || identity || <code>AGE-SECRET-KEY-</code> || the 32-byte seed
     296 | +|-
     297 | +| classic (X25519) || recipient || <code>age</code> || <code>X25519(seed, G)</code>, 32 bytes
     298 | +|-
     299 | +| post-quantum (X-Wing) || identity || <code>AGE-SECRET-KEY-PQ-</code> || the same 32-byte seed
    


    akarve commented at 2:29 AM on August 10, 2026:

    let's be careful here. i think in all cases we want to say "32-byte seed" because in C2SP age we read:

    This recipient type is secure against future cryptographically-relevant quantum computers, so the same file SHOULD NOT be encrypted to both this recipent type and to other non-quantum-resistant recipient types.


    akarve commented at 2:31 AM on August 10, 2026:

    In fact please clarify non-reuse across flavors in one sentence below the table.

  18. in bip-0085.mediawiki:289 in 5055fa2228
     281 | @@ -282,6 +282,42 @@ INPUT:
     282 |  OUTPUT
     283 |  * DERIVED ENTROPY=492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c
     284 |  
     285 | +====<code>age</code> file encryption keys====
     286 | +
     287 | +A 32-byte HEX-application output at <code>m/83696968'/128169'/32'/{index}'</code> is a
     288 | +cryptographically random value usable directly as the secret key of an
     289 | +[https://age-encryption.org/v1 age] file-encryption identity. age has two identity flavours;
    


    akarve commented at 2:31 AM on August 10, 2026:
    [https://age-encryption.org/v1 age] file-encryption identity. age has two identity flavors.
    
  19. in bip-0085.mediawiki:290 in 5055fa2228
     281 | @@ -282,6 +282,42 @@ INPUT:
     282 |  OUTPUT
     283 |  * DERIVED ENTROPY=492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c
     284 |  
     285 | +====<code>age</code> file encryption keys====
     286 | +
     287 | +A 32-byte HEX-application output at <code>m/83696968'/128169'/32'/{index}'</code> is a
     288 | +cryptographically random value usable directly as the secret key of an
     289 | +[https://age-encryption.org/v1 age] file-encryption identity. age has two identity flavours;
     290 | +both encode this 32-byte seed, and the matching recipient (public key) is derived from it:
    


    akarve commented at 2:32 AM on August 10, 2026:
    Both encode a 32-byte seed. The matching recipient (public key) is derived from the seed as follows:
    
  20. in bip-0085.mediawiki:304 in 5055fa2228
     299 | +| post-quantum (X-Wing) || identity || <code>AGE-SECRET-KEY-PQ-</code> || the same 32-byte seed
     300 | +|-
     301 | +| post-quantum (X-Wing) || recipient || <code>age1pq</code> || X-Wing encapsulation key, 1216 bytes
     302 | +|}
     303 | +
     304 | +Every string above is Bech32 (BIP-173) of the form <code>HRP || "1" || encode(payload) || checksum</code>.
    


    akarve commented at 2:34 AM on August 10, 2026:

    Technically Bech32 maxes out at 90 characters and that exception is eliminated? Shouldn't we say "Each string uses the Bech32 character set for the encoded payload, but is not limited in length." or something like that?


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bips. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-08-20 04:10 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me