mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-23 00:45:53 -05:00
108 lines
5.3 KiB
Markdown
108 lines
5.3 KiB
Markdown
NIP-102
|
|
=======
|
|
|
|
Subkey Attestation and Management
|
|
------------------------------
|
|
|
|
`draft` `optional`
|
|
|
|
This NIP defines a way to separate identity from authentication using subkey attestations. This allows the use of one keypair to issue independent keypairs for different apps. If any subkey key is compromised, the root keypair can publish an event revoking the key.
|
|
|
|
## Attestation
|
|
|
|
A subkey attestation is created by signing the message:
|
|
|
|
```
|
|
nip102:<hex-subkey1-pubkey>
|
|
```
|
|
|
|
This signature is then added to events to demonstrate authorization to post on behalf of the original key.
|
|
|
|
The parent key can publish a `Kind 10102` event for subkey management. This event lists attestations that have been revoked, as well as those that should be used to encrypt messages so that they can be read using preferred clients. These declarations are only a preference, as they may not be available in a timely manner.
|
|
|
|
Content:
|
|
```json
|
|
{
|
|
"inbox_keys": ["<hex-subkey1-pubkey>", "<hex-subkey2-pubkey>"],
|
|
"revoked_subkeys": ["<hex-subkey3-pubkey>"]
|
|
}
|
|
```
|
|
|
|
## Event Signing
|
|
|
|
When signing events using a subkey, the account pubkey will be included as a well-known attribute. The event will also include an attestation of the subkey's authorization signed by the main key. Relays and clients should validate this attestation, and discard messages that make invalid claims.
|
|
|
|
```json
|
|
{
|
|
"pubkey": "<hex-subkey1-pubkey>",
|
|
"kind": 1,
|
|
"tags": [
|
|
["I", "<hex-account0-pubkey>"],
|
|
["Ia", "<subkey1-attestation>"]
|
|
]
|
|
}
|
|
```
|
|
|
|
## Event Validation
|
|
|
|
An implementing client's subscriptions will be slightly different. For each subscription using `authors`, a second subscription will be registered specifying `#I` instead of `authors`. This will collect all events published by subkeys of the authors, each of which will be checked for a valid attestation in `Ia`. This can be done at the same time the event signature is validated. As the client finds new values of `I`, it will request `Kind 10102` events with an author of `I` in order to check for revocations. When a `Kind 10102` event with `revoked_attestations` is received, use this index to find events that are no longer valid.
|
|
|
|
After validation, the client will treat an event as if it were signed by the pubkey specified in `I`. This includes honoring NIP-9 deletion requests for events authored by `account0` as well as those having an `I` of `account0` (eg, a subkey acting on behalf of `account0`).
|
|
|
|
If `subkey1` publishes an event without an `I` attribute, it should be treated as having an author of `subkey1` and NOT `account0`. `subkey1` may have its own profile, etc, separate from `account0` that is used in these circumstances.
|
|
|
|
## Destructive Events
|
|
|
|
`NIP-09` deletion requests should be honored for events authored by `account0` or having an `I` of `account0`.
|
|
|
|
## Adoption
|
|
|
|
### Stage 1:
|
|
|
|
Client support is required for basic functionality, so we start when the first client implements this spec.
|
|
|
|
The client will be able to sign messages for the keypair `account0`. A new keypair `subkey1` is generated, and an attestation that `subkey1` is authorized to post on behalf of `account0` is signed by `account0`. For brevity we will say this is a key rotation, and the client now switches to signing messages using `subkey1` with `subkey1-attestation`. The client publishes the message:
|
|
|
|
```json
|
|
{
|
|
"kind": 1,
|
|
"content": "GM",
|
|
"I": "<hex-account0-pubkey>",
|
|
"Ia": "<subkey1-attestation1>"
|
|
}
|
|
```
|
|
|
|
All instances of the client will create secondary subscriptions with `#I` instead of `authors`. Valid events will be merged with those authored by the key in `I`, though destructive events (deletions / replacements) will be deferred until a `Kind 10102` has been retrieved for the pubkey `I`.
|
|
|
|
Clients that do not support this NIP will continue to treat `subkey1` and `account0` as different accounts.
|
|
|
|
### Stage 2:
|
|
|
|
Relays should ensure that `I` is indexed, and may drop events with invalid `Ia` attestations or have been revoked with the expectation that these will be discarded by clients anyway. Replacement events should ignore `I` and maintained per signing key until there is sufficient client support. In the interim conforming clients will merge these locally.
|
|
|
|
## Reference Code
|
|
|
|
```python
|
|
import secp256k1
|
|
|
|
def create_attestation(account_privkey, subkey_pubkey):
|
|
privkey = secp256k1.PrivateKey(bytes.fromhex(account_privkey))
|
|
signature = privkey.ecdsa_sign(bytes.fromhex(subkey_pubkey))
|
|
return privkey.ecdsa_serialize(signature).hex()
|
|
|
|
def verify_attestation(account_pubkey, subkey_pubkey, attestation):
|
|
pubkey = secp256k1.PublicKey(bytes.fromhex(account_pubkey), raw=True)
|
|
signature = pubkey.ecdsa_deserialize(bytes.fromhex(attestation))
|
|
return pubkey.ecdsa_verify(bytes.fromhex(subkey_pubkey), signature)
|
|
|
|
# Example usage
|
|
account_privkey = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
|
account_pubkey = secp256k1.PrivateKey(bytes.fromhex(account_privkey)).pubkey.serialize().hex()
|
|
subkey_pubkey = "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"
|
|
|
|
attestation = create_attestation(account_privkey, subkey_pubkey)
|
|
# attestation is "30440220198c94e388c3a5d7eed7f66ea83dd60a0156ba612c1d5067286ace5c641cbb600220739ca9cd3f3780f28c3a98df954736e323d3f23905bfea4482365055b2fb9fe5"
|
|
|
|
print(f"Attestation is valid: {verify_attestation(account_pubkey, subkey_pubkey, attestation)}")
|
|
```
|