there's just a silly shakespeare export of the ui. i need to make more of a "nip" repo now that i'm seeing the shape of it better. i'll post it when its ready.
Discussion
briefly for now: https://github.com/vcavallo/attestr/blob/main/NIP.md
## Overview
Attestr is a protocol built on Nostr that allows **two parties to mutually sign a statement or promise**, timestamp it, and later attest whether it was honored. It uses existing Nostr primitives (events, tags, signatures) and optionally integrates OpenTimestamps for third-party timestamping.
## Event Kinds
| Kind | Name | Description |
| ------- | -------------------- | ---------------------------------------------- |
| `18973` | Draft Version | Replaceable drafts during negotiation |
| `30677` | Agreement Commitment | Each party signs the agreed hash |
| `35319` | Outcome Attestation | Each party marks "honored" or "disputed" |
## Event Structure
### Draft Version (`kind: 18973`)
Used during negotiation phase. Replaceable events that contain draft contract text. Each party can update their draft by publishing a new event with the same UUID, with only the latest version from each party being stored.
**Required tags:**
- `["d", "
- `["p", "
**Optional tags:**
- `["attestr-conclusion-date", "
- `["alt", "Attestr contract draft"]` - Human-readable description
**Content:** Draft contract text
**Behavior:** Since these are replaceable events (kind 18973), only the latest draft from each party is stored by relays. When a party updates their draft, it replaces their previous version. This keeps the negotiation focused on current positions without cluttering storage with outdated proposals.
**Note:** We use `attestr-conclusion-date` instead of the standard `expiration` tag to avoid conflicts with NIP-40, which would cause relays to automatically reject events past their expiration time.
### Agreement Commitment (`kind: 30677`)
Signed commitment to the final agreed contract hash.
**Required tags:**
- `["d", "
- `["sha256", "
- `["p", "
**Optional tags:**
- `["attestr-conclusion-date", "
- `["ots", "
- `["alt", "Attestr agreement commitment"]` - Human-readable description
**Content:** `agreement_hash=
### Outcome Attestation (`kind: 35319`)
Attestation of whether the contract was honored or disputed.
**Required tags:**
- `["d", "
- `["sha256", "
- `["p", "
**Optional tags:**
- `["alt", "Attestr outcome attestation"]` - Human-readable description
**Content:** `"honored"` or `"disputed"`
## Contract Lifecycle
1. **Negotiation:** Each party posts replaceable drafts (`18973`) until final text is agreed
- Parties can update their drafts by publishing new events with the same UUID
- Only the latest draft from each party is stored (replaceable behavior)
- Negotiation continues until both parties are satisfied with the current terms
2. **Commitment:** Both parties post `30677` signing the same `agreement_hash`
- Once committed, drafts can no longer be edited
- Both parties must sign the exact same contract text hash
3. **Outcome:** After maturity, parties post `35319` with `"honored"` or `"disputed"`
## Agreement Hash Computation
The agreement hash is computed as SHA-256 of the normalized contract text:
```javascript
function computeAgreementHash(contractText) {
const normalized = contractText.trim().replace(/\r\n/g, '\n');
return sha256(normalized);
}
```
## Verification
A contract is considered mutually agreed when:
1. Two distinct pubkeys have published `30677` events
2. Both events have the same UUID (`d` tag)
3. Both events have the same agreement hash (`sha256` tag)
## Contract UUID
Contract UUIDs should be globally unique identifiers, typically in the format `attestr-
Awesome thanks! Would love to implement this directly into Bloom so might have some ideas to throw your way.
Ooh very interesting... What purpose would agreements serve in bloom? Or are you thinking of the general idea of mutually signing
Yeah cosigning PDFs, things like thqt
cool yea that's great! i didn't exhaustively search the nips first to see if there is an existing "two people sign the same hash" thing anywhere - for all i know there is something even better out there. let me know how it goes!
(...and now that i'm thinking of it, any note id is already a hash of its content, so one of my steps is sort of unnecessary..)