migrate to bip340 schnorr signatures.
This commit is contained in:
parent
d80d020cd2
commit
3404dee7d0
|
@ -5,18 +5,18 @@ Basic protocol flow description.
|
|||
--------------------------------
|
||||
|
||||
|
||||
1. Each user has a keypair in the `secp256k1` curve;
|
||||
1. Each user has a keypair. Signatures, public key and encodings are done according to the [Schnorr signatures standard for the curve `secp256k1`](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
|
||||
2. Users can publish `events` to any compatible relay by calling `POST /save_update` with `Content-Type: application/json` and a JSON object describing an event:
|
||||
|
||||
```
|
||||
{
|
||||
id: <32-bytes sha256 of the the serialized event data, optional as will be recomputed by the relay>
|
||||
pubkey: <33-bytes hex-encoded compressed public key of the event creator>,
|
||||
pubkey: <32-bytes hex-encoded public key of the event creator>,
|
||||
created_at: <unix timestamp>,
|
||||
kind: <integer>,
|
||||
ref: <32-bytes hex of the id of another event, optional>,
|
||||
content: <arbitrary string>,
|
||||
sig: <ECDSA signature of the sha256 hash of the serialized event data, which is the same as the "id" field>,
|
||||
sig: <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>,
|
||||
}
|
||||
```
|
||||
3. (The serialization function is strict and standardized, see source code for now)
|
||||
|
@ -25,7 +25,7 @@ Basic protocol flow description.
|
|||
- `0`: `set_metadata`: the `content` is set to a stringified JSON object `{name: <string>, about: <string>, picture: <url, string>}` describing the user who created the event. A relay may delete past `set_metadata` events once it gets a new one for the same pubkey.
|
||||
- `1`: `text_note`: the `content` is set to the text content of a note, anything the user wants to say.
|
||||
- `2`: `recommend_server`: the `content` is set to the URL (e.g., `https://somerelay.com`) of a relay the event creator wants to recommend to its followers.
|
||||
6. A relay MUST serve an SSE (Server-Sent Events) stream at the path `GET /listen_updates`. As querystring parameters it MUST expect `?session=<arbitrary id chosen by the client>` any number of `&key=<33-bytes hex-encoded pubkey>`.
|
||||
6. A relay MUST serve an SSE (Server-Sent Events) stream at the path `GET /listen_updates`. As querystring parameters it MUST expect `?session=<arbitrary id chosen by the client>` any number of `&key=<32-bytes hex-encoded pubkey>`.
|
||||
7. Immediately upon receiving that request the relay MUST return the recent past events from all the received authors with the event type `history`, then it SHOULD keep the connection open and return new events from these same keys as they are received with the event type `happening`. The format of the messages is just the same event object above, as JSON:
|
||||
|
||||
```
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/fiatjaf/schnorr"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -45,12 +45,8 @@ func (evt *Event) Serialize() ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubkey, err := btcec.ParsePubKey(pubkeyb, btcec.S256())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing pubkey: %w", err)
|
||||
}
|
||||
if evt.PubKey != hex.EncodeToString(pubkey.SerializeCompressed()) {
|
||||
return nil, fmt.Errorf("pubkey is not serialized in compressed format")
|
||||
if len(pubkeyb) != 32 {
|
||||
return nil, fmt.Errorf("pubkey must be 32 bytes, not %d", len(pubkeyb))
|
||||
}
|
||||
if _, err = b.Write(pubkeyb); err != nil {
|
||||
return nil, err
|
||||
|
@ -96,19 +92,35 @@ func (evt *Event) Serialize() ([]byte, error) {
|
|||
// (which is a hash of the serialized event content).
|
||||
// returns an error if the signature itself is invalid.
|
||||
func (evt Event) CheckSignature() (bool, error) {
|
||||
// validity of these is checked by Serialize()
|
||||
pubkeyb, _ := hex.DecodeString(evt.PubKey)
|
||||
pubkey, _ := btcec.ParsePubKey(pubkeyb, btcec.S256())
|
||||
// validity of these is checked by Serialize(), which should be called first
|
||||
pubkey, _ := hex.DecodeString(evt.PubKey)
|
||||
|
||||
bsig, err := hex.DecodeString(evt.Sig)
|
||||
hash, _ := hex.DecodeString(evt.ID)
|
||||
if len(hash) != 32 {
|
||||
return false, fmt.Errorf("invalid event id/hash when checking signature (%s)",
|
||||
evt.ID)
|
||||
}
|
||||
|
||||
sig, err := hex.DecodeString(evt.Sig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("signature is invalid hex: %w", err)
|
||||
}
|
||||
signature, err := btcec.ParseDERSignature(bsig, btcec.S256())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse DER signature: %w", err)
|
||||
if len(sig) != 64 {
|
||||
return false, fmt.Errorf("signature must be 64 bytes, not %d", len(sig))
|
||||
}
|
||||
|
||||
hash, _ := hex.DecodeString(evt.ID)
|
||||
return signature.Verify(hash, pubkey), nil
|
||||
var p [32]byte
|
||||
for i, b := range pubkey {
|
||||
p[i] = b
|
||||
}
|
||||
var h [32]byte
|
||||
for i, b := range hash {
|
||||
h[i] = b
|
||||
}
|
||||
var s [64]byte
|
||||
for i, b := range sig {
|
||||
s[i] = b
|
||||
}
|
||||
|
||||
return schnorr.Verify(p, h, s)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module github.com/fiatjaf/profiles
|
||||
module github.com/fiatjaf/nostr-relay
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.21.0-beta
|
||||
github.com/fiatjaf/schnorr v0.2.1-hack
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
|
|
23
relay/go.sum
23
relay/go.sum
|
@ -1,10 +1,9 @@
|
|||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M=
|
||||
github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94=
|
||||
github.com/btcsuite/btcd v0.0.0-20190109040709-5bda5314ca95 h1:bmv+LE3sbjb/M06u2DBi92imeKj7KnCUBOvyZYqI8d8=
|
||||
github.com/btcsuite/btcd v0.0.0-20190109040709-5bda5314ca95/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190112041146-bf1e1be93589/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||
|
@ -16,7 +15,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
|
|||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/fiatjaf/schnorr v0.2.1-hack h1:6NwQNN5O4+ZUm8KliT+l198vDVH8ovv8AJ8CiL4hvs0=
|
||||
github.com/fiatjaf/schnorr v0.2.1-hack/go.mod h1:6aMsVxPxyO6awpdmNkfkJ8vXqsmUOeGCHp2CdG5LPR0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
|
@ -33,6 +33,7 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT
|
|||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
|
@ -50,24 +51,26 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
|||
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/antage/eventsource.v1 v1.0.0-20150318155416-803f4c5af225 h1:xy+AV3uSExoRQc2qWXeZdbhFGwBFK/AmGlrBZEjbvuQ=
|
||||
gopkg.in/antage/eventsource.v1 v1.0.0-20150318155416-803f4c5af225/go.mod h1:SiXNRpUllqhl+GIw2V/BtKI7BUlz+uxov9vBFtXHqh8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -91,9 +91,9 @@ func listenUpdates(w http.ResponseWriter, r *http.Request) {
|
|||
// past events
|
||||
inkeys := make([]string, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
// to prevent sql attack here we will check if these keys are valid 33-byte hex
|
||||
// to prevent sql attack here we will check if these keys are valid 32-byte hex
|
||||
parsed, err := hex.DecodeString(key)
|
||||
if err != nil || len(parsed) != 33 {
|
||||
if err != nil || len(parsed) != 32 {
|
||||
continue
|
||||
}
|
||||
inkeys = append(inkeys, fmt.Sprintf("'%x'", parsed))
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"assert": "^2.0.0",
|
||||
"bip-schnorr": "github:fiatjaf/bip-schnorr",
|
||||
"buffer": "^6.0.2",
|
||||
"dexie": "^3.0.2",
|
||||
"elliptic": "^6.5.3",
|
||||
"insort": "^0.4.0",
|
||||
"pretty-date": "^0.2.0",
|
||||
"quick-lru": "^5.1.1",
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import elliptic from 'elliptic'
|
||||
import Dexie from 'dexie'
|
||||
|
||||
export const ec = new elliptic.ec('secp256k1')
|
||||
export const db = new Dexie('db')
|
||||
|
||||
db.version(1).stores({
|
||||
|
|
|
@ -1,19 +1,41 @@
|
|||
import shajs from 'sha.js'
|
||||
import BigInteger from 'bigi'
|
||||
import schnorr from 'bip-schnorr'
|
||||
|
||||
import {ec} from './globals'
|
||||
import {db} from './globals'
|
||||
|
||||
export function makeRandom32() {
|
||||
var array = new Uint32Array(32)
|
||||
window.crypto.getRandomValues(array)
|
||||
return Buffer.from(array)
|
||||
}
|
||||
|
||||
export function pubkeyFromPrivate(privateHex) {
|
||||
return schnorr.convert
|
||||
.pubKeyFromPrivate(new BigInteger(privateHex, 16))
|
||||
.toString('hex')
|
||||
}
|
||||
|
||||
export function verifySignature(evt) {
|
||||
return true // TODO
|
||||
try {
|
||||
schnorr.verify(
|
||||
Buffer.from(evt.pubkey, 'hex'),
|
||||
Buffer.from(evt.id, 'hex'),
|
||||
Buffer.from(evt.sig, 'hex')
|
||||
)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function publishEvent(evt, key, hosts) {
|
||||
let hash = shajs('sha256').update(serializeEvent(evt)).digest()
|
||||
evt.id = hash.toString('hex')
|
||||
|
||||
evt.sig = ec
|
||||
.keyFromPrivate(key, 'hex')
|
||||
.sign(hash, {canonical: true})
|
||||
.toDER('hex')
|
||||
evt.sig = schnorr
|
||||
.sign(new BigInteger(key, 16), hash, makeRandom32())
|
||||
.toString('hex')
|
||||
|
||||
for (let i = 0; i < hosts.length; i++) {
|
||||
let host = hosts[i]
|
||||
|
|
|
@ -2,8 +2,13 @@ import {createStore, createLogger} from 'vuex'
|
|||
import {SortedMap} from 'insort'
|
||||
import LRU from 'quick-lru'
|
||||
|
||||
import {verifySignature, publishEvent} from './helpers'
|
||||
import {ec, db} from './globals'
|
||||
import {
|
||||
pubkeyFromPrivate,
|
||||
makeRandom32,
|
||||
verifySignature,
|
||||
publishEvent
|
||||
} from './helpers'
|
||||
import {db} from './globals'
|
||||
|
||||
export default createStore({
|
||||
plugins: (process.env.NODE_ENV !== 'production'
|
||||
|
@ -29,7 +34,7 @@ export default createStore({
|
|||
haveEventSource,
|
||||
session: new Date().getTime() + '' + Math.round(Math.random() * 100000),
|
||||
relays,
|
||||
key: ec.genKeyPair().getPrivate('hex'),
|
||||
key: makeRandom32().toString('hex'),
|
||||
following: [],
|
||||
home: new SortedMap(),
|
||||
metadata: new LRU({maxSize: 100}),
|
||||
|
@ -37,8 +42,7 @@ export default createStore({
|
|||
}
|
||||
},
|
||||
getters: {
|
||||
pubKeyHex: state =>
|
||||
ec.keyFromPrivate(state.key, 'hex').getPublic(true, 'hex'),
|
||||
pubKeyHex: state => pubkeyFromPrivate(state.key),
|
||||
writeServers: state =>
|
||||
state.relays
|
||||
.filter(({policy}) => policy.indexOf('w') !== -1)
|
||||
|
@ -54,7 +58,7 @@ export default createStore({
|
|||
state.key = key
|
||||
state.following = following.concat(
|
||||
// always be following thyself
|
||||
ec.keyFromPrivate(state.key, 'hex').getPublic(true, 'hex')
|
||||
pubkeyFromPrivate(state.key)
|
||||
)
|
||||
state.home = home
|
||||
state.metadata = metadata
|
||||
|
|
Loading…
Reference in New Issue