Skip to content
254 changes: 241 additions & 13 deletions bip-0085.mediawiki
Original file line number Diff line number Diff line change
Expand Up @@ -358,36 +358,255 @@ OUTPUT
* DERIVED ENTROPY=f7cfe56f63dca2490f65fcbf9ee63dcd85d18f751b6b5e1c1b8733af6459c904a75e82b4a22efff9b9e69de2144b293aa8714319a054b6cb55826a8e51425209
* DERIVED PWD=_s`{TW89)i4`

===RSA===
===GPG Keys===
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely need test vectors and expected outputs for all applications and sub applications. I can also add it to the reference implementation but won't be able to get to that right away. PRs welcome. https://github.com/akarve/bipsea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to work up some test vectors and a reference implementation once there is some agreement on a general direction :)


Application number: 828365'

The derivation path format is: <code>m/83696968'/828365'/{key_bits}'/{key_index}'</code>
The derivation path format is: <code>m/83696968'/828365'/{key_type}'/{key_bits}'/{key_index}'</code>
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the GPG derivation path from the previously-specified RSA format (which did not include {key_type}) will change the derived material for existing paths and break interoperability with any current implementations/vectors. If backward compatibility is a goal, consider keeping the legacy RSA path intact and allocating new application numbers for ECC (or otherwise defining a legacy/compat mode) rather than inserting a new path level under the same application number.

Copilot uses AI. Check for mistakes.

The RSA key generator should use BIP85-DRNG as the input RNG function.
All path components are hardened (denoted by <code>'</code>). The key_type values are plain integers:

===RSA GPG===
{|
!OpenPGP Key Type
!key_type
|-
| RSA
| 0
|-
| ECC(Curve25519)
| 1
|-
Comment on lines +365 to +378
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The derivation path format already hardens the key_type component ("{key_type}'"), but the table also defines key_type values with a trailing apostrophe (e.g., "0'", "1'"). This is ambiguous and can lead to double-hardening / inconsistent interpretation across implementations. Define key_type as a plain integer (0..4) and state that the path component is always hardened, or remove the apostrophe from the path placeholder.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot code review[agent] Can you add a note for the ECC key_types that mentions which key_bits lengths are valid

| ECC(secp256k1)
| 2
|-
| ECC(NIST)
| 3
|-
| ECC(Brainpool)
| 4
|-
|}

The valid key_bits values and corresponding algorithms for each key_type are:

{|
!key_type
!key_bits
!OpenPGP Algorithm / Curve
|-
| 0 (RSA)
| 1024, 2048, 4096
| RSA
|-
| 1 (Curve25519)
| 256
| Ed25519 (certify/sign/auth), X25519 (encrypt)
|-
| 2 (secp256k1)
| 256
| secp256k1
|-
| 3 (NIST)
| 256, 384, 521
| NIST P-256, P-384, P-521 respectively
|-
| 4 (Brainpool)
| 256, 384, 512
| brainpoolP256r1, brainpoolP384r1, brainpoolP512r1 respectively
|-
|}

For key_types with a fixed curve (1 and 2), key_bits MUST be 256. Any other value is invalid.

BIP85-DRNG MUST be used as the input RNG function when key generation requires more than 64 bytes of random input. This applies to all RSA key sizes and to NIST P-521 (key_bits=521). All other supported ECC key types use the 64-byte HMAC output directly.

<b>ECC Scalar Derivation for Curves Exceeding 64 Bytes</b>

For elliptic curves whose base length (⌈log₂(order) / 8⌉) exceeds the
64-byte HMAC-SHA512 entropy output — currently only NIST P-521 (base
length 66 bytes) — the private scalar MUST be derived using the
BIP85-DRNG (SHAKE256) as follows:

1. Read `baselen` bytes from the DRNG (66 bytes for P-521).

2. Interpret the bytes as a big-endian unsigned integer.

3. Mask the integer to `order.bit_length()` bits:
`scalar = raw_int & ((1 << bit_length) - 1)`

4. If `scalar == 0` or `scalar >= order`, apply the fallback:
`scalar = (scalar % (order - 1)) + 1`

Implementations MUST use bit masking (step 3) rather than direct
modular reduction (`raw_int % order`). The two methods produce
different scalars when the raw integer has more bits than the curve
order, because bit masking discards the top bits while modular
reduction folds them in.

For curves with base length ≤ 64 bytes (all others in this spec),
the scalar is derived directly from the first `baselen` bytes of the
64-byte HMAC-SHA512 entropy, reduced modulo the curve order with the
same fallback.

<b>RSA Key Generation Algorithm</b>

RSA key generation from the BIP85-DRNG MUST use the FIPS 186-4 (§B.3.1,
§C.3.1) algorithm for probable prime generation. Specifically, the
Miller-Rabin primality test MUST use random bases drawn from the same
DRNG (randfunc) that generates prime candidates — NOT fixed or
deterministic witnesses.

The reference algorithm is PyCryptodome's `RSA.generate(key_bits,
randfunc=drng.read)`, which implements FIPS 186-4 with random
Miller-Rabin witnesses drawn from the provided `randfunc`.

Implementations that use fixed Miller-Rabin witnesses (e.g., small
primes 2, 3, 5, …) will consume DRNG bytes at a different rate than
the reference algorithm, producing different primes and therefore
different RSA keys from the same entropy. Such implementations are
NOT compliant with this specification.

The use of random witnesses is also cryptographically stronger, as
fixed small-prime witnesses cannot detect Carmichael numbers that are
strong pseudoprimes to all tested bases.

<b>Primary Keys and Subkeys</b>

In OpenPGP, a key consists of a primary key used for certification and one or more subkeys for encryption, signing, and authentication. This structure separates long-term identity (the primary key) from operational keys (subkeys), allowing subkeys to expire or be revoked independently without affecting the primary key identity.

Keys allocated for GPG purposes use the following scheme:

Keys allocated for RSA-GPG purposes use the following scheme:
* Primary key: <code>m/83696968'/828365'/{key_type}'/{key_bits}'/{key_index}'</code>
* Sub keys: <code>m/83696968'/828365'/{key_type}'/{key_bits}'/{key_index}'/{sub_key}'</code>
** key_index is the parent key for CERTIFY capability
** sub_key <code>0'</code> is used as the ENCRYPTION key
** sub_key <code>1'</code> is used as the AUTHENTICATION key
** sub_key <code>2'</code> is usually used as SIGNATURE key

- Main key <code>m/83696968'/828365'/{key_bits}'/{key_index}'</code>
- Sub keys: <code>m/83696968'/828365'/{key_bits}'/{key_index}'/{sub_key}'</code>
All subkeys SHOULD use the same key_type as the primary key. Mixed key types across primary and subkeys are out of scope for this specification. Additional subkeys may be added following the same role pattern, incrementing the sub_key index.
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Curve25519, OpenPGP typically uses different algorithms for different capabilities (e.g., Ed25519 for certify/sign/auth vs X25519/Curve25519 ECDH for encryption). The current text says all subkeys SHOULD use the same key_type, but doesn’t specify the required per-capability algorithm mapping within that key_type. Please clarify the exact OpenPGP public-key algorithm/curve used for primary vs encryption/auth/sign subkeys under key_type=Curve25519 to avoid incompatible key material generation.

Suggested change
All subkeys SHOULD use the same key_type as the primary key. Mixed key types across primary and subkeys are out of scope for this specification. Additional subkeys may be added following the same role pattern, incrementing the sub_key index.
All subkeys SHOULD use the same key_type (i.e. the same underlying curve family) as the primary key. Mixed key types across primary and subkeys are out of scope for this specification. For <code>key_type = Curve25519</code>, implementations MUST generate OpenPGP keys as follows: the primary CERTIFY key and any SIGNATURE or AUTHENTICATION subkeys MUST use Ed25519 (OpenPGP EdDSA with curve Ed25519), while ENCRYPTION subkeys MUST use X25519/Curve25519 ECDH (OpenPGP ECDH with curve Curve25519). Additional subkeys may be added following the same role pattern, incrementing the sub_key index.

Copilot uses AI. Check for mistakes.

- key_index is the parent key for CERTIFY capability
- sub_key <code>0'</code> is used as the ENCRYPTION key
- sub_key <code>1'</code> is used as the AUTHENTICATION key
- sub_key <code>2'</code> is usually used as SIGNATURE key
For key_type 1 (Curve25519), the OpenPGP algorithm used depends on the key capability, because Curve25519 takes different forms for signing versus encryption in the OpenPGP standard:
* CERTIFY, SIGN, and AUTHENTICATE capabilities use Ed25519 (EdDSA)
* ENCRYPT capability uses X25519 (ECDH)

Note on timestamps:
This means the primary key and sub_keys 1 (auth) and 2 (sign) under key_type 1 produce Ed25519 keys, while sub_key 0 (encrypt) produces an X25519 key. The same derived entropy bytes are interpreted according to the required algorithm.

<b>Note on timestamps:</b>
The resulting RSA key can be used to create a GPG key where the creation date MUST be fixed to UNIX Epoch timestamp 1231006505 (the Bitcoin genesis block time <code>'2009-01-03 18:15:05'</code> UTC)<ref>The human-readable datetime string was incorrectly noted as '2009-01-03 18:05:05' prior to v2.0.0 of this BIP, so implementations that relied on it rather than UNIX Epoch timestamp 1231006505 will produce different key fingerprints.</ref> because the key fingerprint is affected by the creation date (Epoch timestamp 0 was not chosen because of legacy behavior in GNUPG implementations for older keys). Additionally, when importing sub-keys under a key in GNUPG, the system time must be frozen to the same timestamp before importing (e.g. by use of <code>faketime</code>).

Note on GPG key capabilities on smartcard/hardware devices:
All keys derived under this application MUST use a fixed creation date of unix Epoch timestamp 1231006505 (the Bitcoin genesis block time <code>'2009-01-03 18:05:05'</code> UTC) when constructing the OpenPGP key packet, because the key fingerprint is affected by the creation date (Epoch timestamp 0 was not chosen because of legacy behavior in GNUPG implementations for older keys). Additionally, when importing sub-keys under a key in GNUPG, the system time must be frozen to the same timestamp before importing (e.g. by use of <code>faketime</code>).

<b>Note on GPG key capabilities on smartcard/hardware devices:</b>

GPG capable smart-cards SHOULD be loaded as follows: The encryption slot SHOULD be loaded with the ENCRYPTION capable key; the authentication slot SHOULD be loaded with the AUTHENTICATION capable key. The signature capable slot SHOULD be loaded with the SIGNATURE capable key.

However, depending on available slots on the smart-card, and preferred policy, the CERTIFY capable key MAY be flagged with CERTIFY and SIGNATURE capabilities and loaded into the SIGNATURE capable slot (for example where the smart-card has only three slots and the CERTIFY capability is required on the same card). In this case, the SIGNATURE capable sub-key would be disregarded because the CERTIFY capable key serves a dual purpose.

====Test vectors====

INPUT:
* MASTER BIP32 ROOT KEY: <code>xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb</code>

'''Key Type 0 — RSA'''

{| class="wikitable"
! Key Bits
! Path
! Entropy (hex)
! Fingerprint
|-
| 1024
| <code>m/83696968'/828365'/0'/1024'/0'</code>
| <code>2b9380df43421f46b5c38e13ea80612ff53488bc5d272e86d493ee1eecf738bb7b50e4978b7352f95772f1211483b0e6bba86c544a946b10d76ed493b8c2e01f</code>
| <code>874A 3964 4ED0 255D EEC1 8E0E 1E63 8864 9672 CF70</code>
|-
| 2048
| <code>m/83696968'/828365'/0'/2048'/0'</code>
| <code>98c4fb6d76f203e8828bdfd28416edca7a83a9b203901f7ad31f056cda8b3c25b19e5fd2aa642ca0abb9ed8bebf3d141af6c76b28a19eba624bdc6f8a76ce138</code>
| <code>9987 9DF6 D21E 34C8 A086 A4BD 8B44 8E5B C298 294A</code>
|-
| 4096
| <code>m/83696968'/828365'/0'/4096'/0'</code>
| <code>2d2ef3335dc51e7a0642bfe86fba0bb4e8401b703d8d679bb1a31d75f8a81f1fd52b20b2eae50ef6e0378b8755f4f0426c68b54f11edc0c848e017e81bb2ad87</code>
| <code>24C2 5A48 383E 1175 4687 1767 D9A0 5CA6 4F2F 6A85</code>
|}

'''Key Type 1 — Curve25519'''

{| class="wikitable"
! Key Bits
! Path
! Entropy (hex)
! Fingerprint
|-
| 256
| <code>m/83696968'/828365'/1'/256'/0'</code>
| <code>0e90b553528cd97a033c282f54cf72c1020adaec205d5c0e57e9f2556d06fea683618e4be8f91e7e059647f9d6373eb8b5f535e7ba4097cfb3e93c4957843614</code>
| <code>E81D F237 1408 2AD2 747E 732B 9A24 C95B D8C2 A55E</code>
|}

'''Key Type 2 — secp256k1'''

{| class="wikitable"
! Key Bits
! Path
! Entropy (hex)
! Fingerprint
|-
| 256
| <code>m/83696968'/828365'/2'/256'/0'</code>
| <code>f3bb8b3d6b81fbd202c34b59ce7e97c83969e9b5733b936de16c51119c7a48239ddf66729ef5e4df97ea39471f05a89f070869b3f9d72d69f3ae8bd7ee4fb6b3</code>
| <code>6D99 D348 74C6 E88F F30C 758A 46F7 E1AF 05FC 3414</code>
|}

'''Key Type 3 — NIST (P-256, P-384, P-521)'''

{| class="wikitable"
! Key Bits
! Path
! Entropy (hex)
! Fingerprint
|-
| 256
| <code>m/83696968'/828365'/3'/256'/0'</code>
| <code>f52586f58521916b9f28b0058be86effcde82e571eabada9e3f63c6f67752ff12a4d3bf2fffe0f147164945691605a58f28f6bded869c38b3db9f0e577d83728</code>
| <code>2FE6 D862 FF2A BF1C 1FAA 2753 B681 BEF5 B5D5 74C4</code>
|-
| 384
| <code>m/83696968'/828365'/3'/384'/0'</code>
| <code>830005ea400f7a03c27aa06a9728fe311c9a48dc31bd417f07b96c69edc73d25baa00d04b9dbbe6f42539b06d9ef1ba62ed73d4a3a992302aae09e17e0d9f42f</code>
| <code>5668 7C3C 9072 19B2 9FCE 39CF 95F0 16F9 B150 B8A1</code>
|-
| 521
| <code>m/83696968'/828365'/3'/521'/0'</code>
| <code>3524b3cbe60eb78a156dae44674702f69381afe5292d6d15d7801b7e530f2a0616b7b876c0ba85d6e675587fdc0ce2242ad00252493ec9c3a024217d1e2aa954</code>
| <code>AAAA 594C 6DB9 5F6B B622 5DCA 10CC E597 28A4 7DBC</code>
|}

'''Key Type 4 — Brainpool (P-256, P-384, P-512)'''

{| class="wikitable"
! Key Bits
! Path
! Entropy (hex)
! Fingerprint
|-
| 256
| <code>m/83696968'/828365'/4'/256'/0'</code>
| <code>97ee4490d89bf257e9a038e2af12824fba47fec721970ca1fc1c094650d2716d75491402530776ba31d215fac6c2de0cb6661f1d380b682e20246bf962cdf385</code>
| <code>6161 7C06 F6F2 AC32 3D67 782F 11CB 4B79 FEFD 4369</code>
|-
| 384
| <code>m/83696968'/828365'/4'/384'/0'</code>
| <code>3fa833db4195fbd7a9c4e3f6fdb65ffb8951c5c65ca0cce441a4410e11aa96fcb094ed8c1fb5317448ae098ca9cae2c351b513e47d1b74e4c80c1facdf7b0a5a</code>
| <code>3278 6624 D0CA 7D7F 0133 0940 397F 2F1F A2BE 47CB</code>
|-
| 512
| <code>m/83696968'/828365'/4'/512'/0'</code>
| <code>985f0131503109fc7fb2ab15e6a86846888e4b9a9f4f11f0d7b30dba4570cf8cc728a4c8ce9bbeb9b9819fbe924bb2d6d71a9c8332635cfb5db5008364f3a43a</code>
| <code>99D7 BDC9 37AC 6E9B CC17 D093 6643 E050 1D03 C680</code>
|}

===DICE===

Application number: 89101'
Expand Down Expand Up @@ -441,6 +660,15 @@ BIP32, BIP39

==Changelog==

===3.0.0 (2026-02-27)===

====Added====

* ECC GPG key types for OpenPGP under application 828365': Curve25519/key_type=1, secp256k1/key_type=2, NIST/key_type=3, Brainpool/key_type=4

====Changed====

* RSA application 828365' (introduced in 1.1.0) extended with a <code>key_type</code> path component (key_type=0 for RSA) to unify all OpenPGP key types under a single application number; RSA derivation path changed from <code>m/83696968'/828365'/{key_bits}'/{key_index}'</code> to <code>m/83696968'/828365'/0'/{key_bits}'/{key_index}'</code> (breaking change for existing RSA implementations)
===2.0.0 (2025-09-19)===

====Fixed====
Expand Down