Login to reply
Replies (1)
## 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", "<uuid>"]` - Contract UUID
- `["p", "<counterparty-pubkey>"]` - Counterparty public key
**Optional tags:**
- `["attestr-conclusion-date", "<unix-timestamp>"]` - Optional maturity date as UNIX timestamp (string)
- `["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", "<uuid>"]` - Contract UUID
- `["sha256", "<hash>"]` - SHA-256 hash of final contract text
- `["p", "<counterparty-pubkey>"]` - Counterparty public key
**Optional tags:**
- `["attestr-conclusion-date", "<unix-timestamp>"]` - Optional maturity date as UNIX timestamp
- `["ots", "<base64>"]` - OpenTimestamps proof
- `["alt", "Attestr agreement commitment"]` - Human-readable description
**Content:** `agreement_hash=<hash>`
### Outcome Attestation (`kind: 35319`)
Attestation of whether the contract was honored or disputed.
**Required tags:**
- `["d", "<uuid>"]` - Contract UUID
- `["sha256", "<hash>"]` - SHA-256 hash of original contract
- `["p", "<counterparty-pubkey>"]` - Counterparty public key
**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-<uuid-v4>`.