package nostr // This is torly's hand-written JSON codec for the wire path. It follows the // Moxie/Nostr decoder: a single pass over the bytes, object keys dispatched // explicitly, and no encoding/json. Object keys are compared as strings rather // than by key[0], and an empty key is rejected before any indexing; `{"":1}` // is legal JSON and previously caused an index-out-of-range panic. // // String decoding validates every escape, rejects C0 controls, and bounds the // surrogate-pair lookahead correctly (i+10 < len, not <=). import ( "errors" "fmt" "strconv" "strings" "unicode/utf8" ) var ( errJSONUnexpected = errors.New("nostr: unexpected JSON") errJSONEmptyKey = errors.New("nostr: empty JSON object key") ) type jparser struct { s []byte i int } func (p *jparser) ws() { for p.i < len(p.s) { switch p.s[p.i] { case ' ', '\t', '\n', '\r': p.i++ default: return } } } func (p *jparser) eof() bool { return p.i >= len(p.s) } func (p *jparser) expect(c byte) error { if p.i >= len(p.s) || p.s[p.i] != c { return fmt.Errorf("%w: expected %q at %d", errJSONUnexpected, c, p.i) } p.i++ return nil } func (p *jparser) finish() error { p.ws() if !p.eof() { return fmt.Errorf("%w: trailing data at %d", errJSONUnexpected, p.i) } return nil } // ParseJSONString parses exactly one JSON string literal. func ParseJSONString(raw []byte) (string, error) { p := &jparser{s: raw} p.ws() v, err := p.parseString() if err != nil { return "", err } if err := p.finish(); err != nil { return "", err } return v, nil } // SplitArray splits a JSON array into its raw element slices. It is used for // relay messages such as ["EVENT",{...}] without allocating an interface tree. func SplitArray(raw []byte) ([][]byte, error) { p := &jparser{s: raw} p.ws() if err := p.expect('['); err != nil { return nil, err } p.ws() if !p.eof() && p.s[p.i] == ']' { p.i++ if err := p.finish(); err != nil { return nil, err } return nil, nil } var out [][]byte for { p.ws() start := p.i if err := p.skipValue(); err != nil { return nil, err } part := make([]byte, p.i-start) copy(part, p.s[start:p.i]) out = append(out, part) p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated array", errJSONUnexpected) } switch p.s[p.i] { case ',': p.i++ case ']': p.i++ if err := p.finish(); err != nil { return nil, err } return out, nil default: return nil, fmt.Errorf("%w: expected , or ] at %d", errJSONUnexpected, p.i) } } } func (p *jparser) parseString() (string, error) { if err := p.expect('"'); err != nil { return "", err } out := make([]byte, 0, 32) for p.i < len(p.s) { c := p.s[p.i] switch { case c == '"': p.i++ if !utf8.Valid(out) { return "", fmt.Errorf("%w: invalid UTF-8 in string", errJSONUnexpected) } return string(out), nil case c == '\\': p.i++ if p.i >= len(p.s) { return "", fmt.Errorf("%w: unterminated escape", errJSONUnexpected) } switch p.s[p.i] { case '"', '\\', '/': out = append(out, p.s[p.i]) p.i++ case 'b': out = append(out, '\b') p.i++ case 'f': out = append(out, '\f') p.i++ case 'n': out = append(out, '\n') p.i++ case 'r': out = append(out, '\r') p.i++ case 't': out = append(out, '\t') p.i++ case 'u': r, err := p.parseUnicodeEscape() if err != nil { return "", err } out = append(out, string(rune(r))...) default: return "", fmt.Errorf("%w: bad escape \\%c", errJSONUnexpected, p.s[p.i]) } case c < 0x20: return "", fmt.Errorf("%w: control byte in string", errJSONUnexpected) default: out = append(out, c) p.i++ } } return "", fmt.Errorf("%w: unterminated string", errJSONUnexpected) } // parseUnicodeEscape is called with p.i at 'u'. It consumes the four hex // digits and an optional low surrogate, with a bounds check that cannot run // past the end of the input. func (p *jparser) parseUnicodeEscape() (rune, error) { if p.i+4 >= len(p.s) { return 0, fmt.Errorf("%w: short \\u escape", errJSONUnexpected) } hi, ok := hex4(p.s[p.i+1 : p.i+5]) if !ok { return 0, fmt.Errorf("%w: bad \\u escape", errJSONUnexpected) } p.i += 5 // 'u' + four digits if hi >= 0xD800 && hi <= 0xDBFF { // A high surrogate must be followed by a low surrogate: six more // bytes, \uXXXX. The strict < is what prevents s[i+10] on truncation. if p.i+6 > len(p.s) || p.s[p.i] != '\\' || p.s[p.i+1] != 'u' { return 0, fmt.Errorf("%w: unpaired surrogate", errJSONUnexpected) } lo, ok := hex4(p.s[p.i+2 : p.i+6]) if !ok || lo < 0xDC00 || lo > 0xDFFF { return 0, fmt.Errorf("%w: bad low surrogate", errJSONUnexpected) } p.i += 6 return rune(0x10000 + (hi-0xD800)*0x400 + (lo - 0xDC00)), nil } if hi >= 0xDC00 && hi <= 0xDFFF { return 0, fmt.Errorf("%w: lone low surrogate", errJSONUnexpected) } return rune(hi), nil } func hex4(b []byte) (int, bool) { if len(b) != 4 { return 0, false } v := 0 for _, c := range b { var d int switch { case c >= '0' && c <= '9': d = int(c - '0') case c >= 'a' && c <= 'f': d = int(c-'a') + 10 case c >= 'A' && c <= 'F': d = int(c-'A') + 10 default: return 0, false } v = v<<4 | d } return v, true } func (p *jparser) parseInt64() (int64, error) { p.ws() start := p.i if p.i < len(p.s) && p.s[p.i] == '-' { p.i++ } digits := false for p.i < len(p.s) && p.s[p.i] >= '0' && p.s[p.i] <= '9' { p.i++ digits = true } if !digits { return 0, fmt.Errorf("%w: expected integer at %d", errJSONUnexpected, start) } if p.i < len(p.s) && (p.s[p.i] == '.' || p.s[p.i] == 'e' || p.s[p.i] == 'E') { return 0, fmt.Errorf("%w: non-integer number at %d", errJSONUnexpected, start) } return strconv.ParseInt(string(p.s[start:p.i]), 10, 64) } func (p *jparser) parseStringArray() ([]string, error) { p.ws() if err := p.expect('['); err != nil { return nil, err } p.ws() if !p.eof() && p.s[p.i] == ']' { p.i++ return nil, nil } var out []string for { p.ws() v, err := p.parseString() if err != nil { return nil, err } out = append(out, v) p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated array", errJSONUnexpected) } switch p.s[p.i] { case ',': p.i++ case ']': p.i++ return out, nil default: return nil, fmt.Errorf("%w: expected , or ]", errJSONUnexpected) } } } func (p *jparser) parseIntArray() ([]int, error) { p.ws() if err := p.expect('['); err != nil { return nil, err } p.ws() if !p.eof() && p.s[p.i] == ']' { p.i++ return nil, nil } var out []int for { n, err := p.parseInt64() if err != nil { return nil, err } out = append(out, int(n)) p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated array", errJSONUnexpected) } switch p.s[p.i] { case ',': p.i++ case ']': p.i++ return out, nil default: return nil, fmt.Errorf("%w: expected , or ]", errJSONUnexpected) } } } func (p *jparser) parseTags() (Tags, error) { p.ws() if err := p.expect('['); err != nil { return nil, err } p.ws() if !p.eof() && p.s[p.i] == ']' { p.i++ return nil, nil } var tags Tags for { p.ws() inner, err := p.parseStringArray() if err != nil { return nil, err } tags = append(tags, Tag(inner)) p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated tags", errJSONUnexpected) } switch p.s[p.i] { case ',': p.i++ case ']': p.i++ return tags, nil default: return nil, fmt.Errorf("%w: expected , or ] in tags", errJSONUnexpected) } } } func (p *jparser) skipValue() error { p.ws() if p.eof() { return fmt.Errorf("%w: missing value", errJSONUnexpected) } switch p.s[p.i] { case '"': _, err := p.parseString() return err case '{': p.i++ p.ws() if !p.eof() && p.s[p.i] == '}' { p.i++ return nil } for { p.ws() if _, err := p.parseString(); err != nil { return err } p.ws() if err := p.expect(':'); err != nil { return err } if err := p.skipValue(); err != nil { return err } p.ws() if p.eof() { return fmt.Errorf("%w: unterminated object", errJSONUnexpected) } switch p.s[p.i] { case ',': p.i++ case '}': p.i++ return nil default: return fmt.Errorf("%w: expected , or }", errJSONUnexpected) } } case '[': p.i++ p.ws() if !p.eof() && p.s[p.i] == ']' { p.i++ return nil } for { if err := p.skipValue(); err != nil { return err } p.ws() if p.eof() { return fmt.Errorf("%w: unterminated array", errJSONUnexpected) } switch p.s[p.i] { case ',': p.i++ case ']': p.i++ return nil default: return fmt.Errorf("%w: expected , or ]", errJSONUnexpected) } } case 't': return p.literal("true") case 'f': return p.literal("false") case 'n': return p.literal("null") default: start := p.i if p.s[p.i] == '-' { p.i++ } digits := false for p.i < len(p.s) && ((p.s[p.i] >= '0' && p.s[p.i] <= '9') || p.s[p.i] == '.' || p.s[p.i] == 'e' || p.s[p.i] == 'E' || p.s[p.i] == '+' || p.s[p.i] == '-') { p.i++ digits = true } if !digits { return fmt.Errorf("%w: bad value at %d", errJSONUnexpected, start) } return nil } } func (p *jparser) literal(v string) error { if p.i+len(v) > len(p.s) || string(p.s[p.i:p.i+len(v)]) != v { return fmt.Errorf("%w: expected %s", errJSONUnexpected, v) } p.i += len(v) return nil } // ParseEvent decodes one NIP-01 event object. Unlike encoding/json, this // decoder never indexes an object key without checking it: an empty key is an // error, not a panic. func ParseEvent(data []byte) (*Event, error) { p := &jparser{s: data} p.ws() if err := p.expect('{'); err != nil { return nil, err } e := &Event{} var haveID, havePub, haveCreated, haveKind, haveTags, haveContent, haveSig bool first := true for { p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated event object", errJSONUnexpected) } if p.s[p.i] == '}' { p.i++ break } if !first { if err := p.expect(','); err != nil { return nil, err } p.ws() } key, err := p.parseString() if err != nil { return nil, err } if key == "" { return nil, errJSONEmptyKey } p.ws() if err := p.expect(':'); err != nil { return nil, err } p.ws() switch key { case "id": v, err := p.parseString() if err != nil { return nil, err } if err := Decode32(v, e.ID[:]); err != nil { return nil, fmt.Errorf("%w: %q", ErrBadID, v) } haveID = true case "pubkey": v, err := p.parseString() if err != nil { return nil, err } if err := Decode32(v, e.PubKey[:]); err != nil { return nil, fmt.Errorf("%w: %q", ErrBadPubKey, v) } havePub = true case "created_at": n, err := p.parseInt64() if err != nil { return nil, fmt.Errorf("%w: %v", ErrBadCreated, err) } e.CreatedAt = n haveCreated = true case "kind": n, err := p.parseInt64() if err != nil || n < 0 || n > MaxKind { return nil, fmt.Errorf("%w: %d", ErrBadKind, n) } e.Kind = int(n) haveKind = true case "tags": tags, err := p.parseTags() if err != nil { return nil, err } e.Tags = tags haveTags = true case "content": v, err := p.parseString() if err != nil { return nil, err } e.Content = v haveContent = true case "sig": v, err := p.parseString() if err != nil { return nil, err } if err := DecodeRaw(v, e.Sig[:]); err != nil { return nil, fmt.Errorf("%w: %q", ErrBadSig, v) } haveSig = true default: if err := p.skipValue(); err != nil { return nil, err } } first = false } if err := p.finish(); err != nil { return nil, err } if !(haveID && havePub && haveCreated && haveKind && haveTags && haveContent && haveSig) { return nil, errors.New("nostr: event is missing a required field") } return e, nil } // ExtractID scans an object for a valid id without requiring the rest of the // event to be well formed. It is used to address an OK response to a malformed // event. An empty key is skipped, never indexed. func ExtractID(data []byte) string { p := &jparser{s: data} p.ws() if err := p.expect('{'); err != nil { return "" } for { p.ws() if p.eof() || p.s[p.i] == '}' { return "" } key, err := p.parseString() if err != nil { return "" } p.ws() if err := p.expect(':'); err != nil { return "" } p.ws() if key == "id" { v, err := p.parseString() if err != nil || !IsHex64(v) { return "" } return v } if err := p.skipValue(); err != nil { return "" } p.ws() if p.eof() { return "" } if p.s[p.i] == ',' { p.i++ continue } return "" } } // parseGraphQuery decodes the _graph filter extension. Both the canonical // four-element array and the legacy object form are accepted. func (p *jparser) parseGraphQuery() (*GraphQuery, error) { p.ws() if p.eof() { return nil, fmt.Errorf("%w: missing _graph value", errJSONUnexpected) } switch p.s[p.i] { case '[': return p.parseGraphArray() case '{': return p.parseGraphObject() default: return nil, fmt.Errorf("%w: _graph must be an array or object", errJSONUnexpected) } } // parseGraphArray decodes ["", , "", ""]. func (p *jparser) parseGraphArray() (*GraphQuery, error) { if err := p.expect('['); err != nil { return nil, err } q := &GraphQuery{} for i := 0; i < 4; i++ { p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated _graph array", errJSONUnexpected) } switch i { case 0: v, err := p.parseString() if err != nil { return nil, fmt.Errorf("_graph seed: %w", err) } q.Seed = v case 1: n, err := p.parseInt64() if err != nil { return nil, fmt.Errorf("_graph depth: %w", err) } if n > GraphMaxDepth { return nil, errGraphDepth } q.Depth = int(n) case 2: v, err := p.parseString() if err != nil { return nil, fmt.Errorf("_graph edge: %w", err) } q.Edge = v case 3: v, err := p.parseString() if err != nil { return nil, fmt.Errorf("_graph direction: %w", err) } q.Direction = v } p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated _graph array", errJSONUnexpected) } if i < 3 { if err := p.expect(','); err != nil { return nil, err } } else if p.s[p.i] != ']' { return nil, fmt.Errorf("%w: _graph needs exactly four elements", errJSONUnexpected) } } p.i++ // ']' if err := q.normalize(); err != nil { return nil, err } return q, nil } // parseGraphObject decodes the object form. Unknown keys are skipped, which // keeps the extension forward-compatible without making an empty key legal. func (p *jparser) parseGraphObject() (*GraphQuery, error) { if err := p.expect('{'); err != nil { return nil, err } q := &GraphQuery{} first := true for { p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated _graph object", errJSONUnexpected) } if p.s[p.i] == '}' { p.i++ break } if !first { if err := p.expect(','); err != nil { return nil, err } p.ws() } key, err := p.parseString() if err != nil { return nil, err } if key == "" { return nil, errJSONEmptyKey } p.ws() if err := p.expect(':'); err != nil { return nil, err } p.ws() switch strings.ToLower(key) { case "pubkey", "seed": v, err := p.parseString() if err != nil { return nil, fmt.Errorf("_graph %s: %w", key, err) } q.Seed = v case "depth": n, err := p.parseInt64() if err != nil { return nil, fmt.Errorf("_graph depth: %w", err) } if n > GraphMaxDepth { return nil, errGraphDepth } q.Depth = int(n) case "edge": v, err := p.parseString() if err != nil { return nil, fmt.Errorf("_graph edge: %w", err) } q.Edge = v case "direction": v, err := p.parseString() if err != nil { return nil, fmt.Errorf("_graph direction: %w", err) } q.Direction = v default: if err := p.skipValue(); err != nil { return nil, err } } first = false } if err := q.normalize(); err != nil { return nil, err } return q, nil } // ParseFilter decodes one NIP-01 filter object. As with events, keys are // compared as strings and an empty key is an explicit error. func ParseFilter(raw []byte) (*Filter, error) { p := &jparser{s: raw} p.ws() if err := p.expect('{'); err != nil { return nil, fmt.Errorf("%w: %v", errBadFilter, err) } f := &Filter{} first := true for { p.ws() if p.eof() { return nil, fmt.Errorf("%w: unterminated filter", errBadFilter) } if p.s[p.i] == '}' { p.i++ break } if !first { if err := p.expect(','); err != nil { return nil, fmt.Errorf("%w: %v", errBadFilter, err) } p.ws() } key, err := p.parseString() if err != nil { return nil, fmt.Errorf("%w: %v", errBadFilter, err) } if key == "" { return nil, fmt.Errorf("%w: %v", errBadFilter, errJSONEmptyKey) } p.ws() if err := p.expect(':'); err != nil { return nil, fmt.Errorf("%w: %v", errBadFilter, err) } p.ws() switch key { case "ids": values, err := p.parseStringArray() if err != nil { return nil, fmt.Errorf("%w: ids: %v", errBadFilter, err) } for _, id := range values { if !IsHex64(id) { return nil, fmt.Errorf("%w: id %q is not 64 lowercase hex characters", errBadFilter, id) } } f.IDs = values case "authors": values, err := p.parseStringArray() if err != nil { return nil, fmt.Errorf("%w: authors: %v", errBadFilter, err) } for _, a := range values { if !IsHex64(a) { return nil, fmt.Errorf("%w: author %q is not 64 lowercase hex characters", errBadFilter, a) } } f.Authors = values case "kinds": values, err := p.parseIntArray() if err != nil { return nil, fmt.Errorf("%w: kinds: %v", errBadFilter, err) } for _, k := range values { if k < 0 || k > MaxKind { return nil, fmt.Errorf("%w: kind %d", errBadFilter, k) } } f.Kinds = values case "since": n, err := p.parseInt64() if err != nil { return nil, fmt.Errorf("%w: since: %v", errBadFilter, err) } f.Since = n case "until": n, err := p.parseInt64() if err != nil { return nil, fmt.Errorf("%w: until: %v", errBadFilter, err) } f.Until = n case "limit": n, err := p.parseInt64() if err != nil { return nil, fmt.Errorf("%w: limit: %v", errBadFilter, err) } if n > 0 { if n > 1<<31-1 { n = 1<<31 - 1 } f.Limit = int(n) } case "search": v, err := p.parseString() if err != nil { return nil, fmt.Errorf("%w: search: %v", errBadFilter, err) } f.Search = v case "_graph": q, err := p.parseGraphQuery() if err != nil { return nil, fmt.Errorf("%w: _graph: %v", errBadFilter, err) } f.Graph = q default: if len(key) >= 2 && key[0] == '#' { values, err := p.parseStringArray() if err != nil { return nil, fmt.Errorf("%w: %s: %v", errBadFilter, key, err) } if f.Tags == nil { f.Tags = make(map[string][]string, 1) } f.Tags[key[1:]] = values } else if err := p.skipValue(); err != nil { return nil, fmt.Errorf("%w: %s: %v", errBadFilter, key, err) } } first = false } if err := p.finish(); err != nil { return nil, fmt.Errorf("%w: %v", errBadFilter, err) } return f, nil }