v0.16: drand's JSON is read strictly

JSON of RFC 8259 in UTF-8 whose value is an object; no object repeats a
name, and names are compared exactly once their escapes are decoded, so
"round" is round and ROUND another name; an escape of a lone surrogate
is malformed; round is a number without sign, fraction or exponent from 1
to 2^53 - 1; signature and randomness are strings (spec v0.16, 47.1). Go's
encoding/json kept the last of two repeated names and matched ROUND to
round. ParseDrandJSON, exported, is the one reader of drand's JSON:
provider/drand reads the answers of the relays with it too. The error
texts do not change.

release.json gains 25 cases of drand's JSON for v0.16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
dev 4 hours ago
parent 7e3b8104a6
commit b57033813d

@ -252,6 +252,35 @@ func ReleaseVectors() (ReleaseVectorFile, error) {
{"8193 bytes", `{"round":1000,"signature":"` + s1000 + `"}` + strings.Repeat(" ", 8193-len(`{"round":1000,"signature":"`+s1000+`"}`)), 1000, invalid},
{"round 1001 for a DateKey of round 1000", `{"round":1001,"signature":"` + s1001 + `"}`, 1000, roundMismatch},
{"the signature of round 1001 as round 1000", `{"round":1000,"signature":"` + s1001 + `"}`, 1000, invalid},
// The strict reading of spec v0.16, §47.1: no repeated name, names
// compared exactly once their escapes are decoded, and the round an
// integer from 1 to 2^53 - 1 without fraction or exponent.
{"round twice", `{"round":1000,"round":1001,"signature":"` + s1000 + `"}`, 1000, invalid},
{`round twice, once escaped as round`, `{"round":1000,"round":1000,"signature":"` + s1000 + `"}`, 1000, invalid},
{"round twice, the second null", `{"round":1000,"round":null,"signature":"` + s1000 + `"}`, 1000, invalid},
{`round escaped as round`, `{"round":1000,"signature":"` + s1000 + `"}`, 1000, ok},
{"Round instead of round", `{"Round":1000,"signature":"` + s1000 + `"}`, 1000, invalid},
{"ROUND beside round: another name, ignored", `{"round":1000,"ROUND":1001,"signature":"` + s1000 + `"}`, 1000, ok},
{"round null", `{"round":null,"signature":"` + s1000 + `"}`, 1000, invalid},
{"round 1000.0", `{"round":1000.0,"signature":"` + s1000 + `"}`, 1000, invalid},
{"round 1e3", `{"round":1e3,"signature":"` + s1000 + `"}`, 1000, invalid},
{"round -1000", `{"round":-1000,"signature":"` + s1000 + `"}`, 1000, invalid},
{"round 0", `{"round":0,"signature":"` + s1000 + `"}`, 1000, invalid},
{"round 2^53", `{"round":9007199254740992,"signature":"` + s1000 + `"}`, 1000, invalid},
{"round 2^53 - 1 for a DateKey of round 1000", `{"round":9007199254740991,"signature":"` + s1000 + `"}`, 1000, roundMismatch},
{"round with a leading zero", `{"round":01000,"signature":"` + s1000 + `"}`, 1000, invalid},
{"signature twice", `{"round":1000,"signature":"` + s1000 + `","signature":"` + s1000 + `"}`, 1000, invalid},
{"signature null", `{"round":1000,"signature":null}`, 1000, invalid},
{"randomness empty", `{"round":1000,"randomness":"","signature":"` + s1000 + `"}`, 1000, invalid},
{"randomness null", `{"round":1000,"randomness":null,"signature":"` + s1000 + `"}`, 1000, invalid},
{"another name twice", `{"round":1000,"signature":"` + s1000 + `","note":1,"note":2}`, 1000, invalid},
{"a name twice in a nested object", `{"round":1000,"signature":"` + s1000 + `","meta":{"a":1,"a":2}}`, 1000, invalid},
{"nested objects and arrays, ignored", `{"round":1000,"signature":"` + s1000 + `","meta":{"a":[1,{"a":2}],"b":{},"c":[]}}`, 1000, ok},
{"a lone surrogate in another name", `{"round":1000,"signature":"` + s1000 + `","\ud800":1}`, 1000, invalid},
{"a lone low surrogate in a value", `{"round":1000,"signature":"` + s1000 + `","note":"\udc00"}`, 1000, invalid},
{"a surrogate pair in a value", `{"round":1000,"signature":"` + s1000 + `","note":"😀"}`, 1000, ok},
{"a tab inside a string", `{"round":1000,"signature":"` + s1000 + `","note":"a` + "\t" + `b"}`, 1000, invalid},
{"something after the object", `{"round":1000,"signature":"` + s1000 + `"}{}`, 1000, invalid},
}
for _, c := range jsons {
v := ReleaseVector{Name: c.name, Input: c.input, Round: c.round}

@ -11,9 +11,6 @@ package drand
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
@ -146,29 +143,14 @@ func (c *Client) fetch(ctx context.Context, relay string, p *profile.Profile, co
if len(b) > maxResponseSize {
return provider.Release{}, fmt.Errorf("%s: response larger than %d bytes: %w", relay, maxResponseSize, datekeys.ErrReleaseInvalid)
}
var wire struct {
Round uint64 `json:"round"`
Signature string `json:"signature"`
Randomness string `json:"randomness"`
}
if err := json.Unmarshal(b, &wire); err != nil {
return provider.Release{}, fmt.Errorf("%s: malformed response: %w", relay, datekeys.ErrReleaseInvalid)
}
sig, err := hex.DecodeString(wire.Signature)
// The answer is read as a release the caller gives, with the strict
// rules of spec v0.16, §47.1, randomness included.
release, err := provider.ParseDrandJSON(b)
if err != nil {
return provider.Release{}, fmt.Errorf("%s: signature is not hex: %w", relay, datekeys.ErrReleaseInvalid)
return provider.Release{}, fmt.Errorf("%s: %w", relay, err)
}
release := provider.Release{Round: wire.Round, Signature: sig}
if err := provider.Verify(p, cond, release); err != nil {
return provider.Release{}, fmt.Errorf("%s: %w", relay, err)
}
// The v2 API omits randomness; if a relay supplies it, it must be
// SHA-256 of the verified signature.
if wire.Randomness != "" {
sum := sha256.Sum256(sig)
if !strings.EqualFold(wire.Randomness, hex.EncodeToString(sum[:])) {
return provider.Release{}, fmt.Errorf("%s: randomness does not match the signature: %w", relay, datekeys.ErrReleaseInvalid)
}
}
return release, nil
}

@ -0,0 +1,298 @@
package provider
import (
"strings"
"unicode/utf8"
)
// The strict reading of drand's JSON (spec v0.16, §47.1): JSON of RFC 8259,
// in UTF-8, whose value is an object; no object of the JSON repeats a name,
// and names are compared exactly, code point by code point, once their
// escapes are decoded, so that "round" is round and Round is another
// name; and an escape of a surrogate that does not pair with the next one
// makes the JSON malformed. A common JSON reader keeps the last of two
// repeated names, or does not tell upper from lower case in them, and two
// readers would see two rounds in the same input.
// jsonMember is a member of the outer object: its name, decoded, the kind of
// its value (a byte of `"`, `0`, `{`, `[`, `t`, `f` or `n`), the text of the
// value as written, and for a string, the string decoded.
type jsonMember struct {
name string
kind byte
raw []byte
str string
}
// strictJSON reads b as a JSON object with the rules above, and returns the
// members of the outer object in their order, or false when b breaks one.
func strictJSON(b []byte) ([]jsonMember, bool) {
if !utf8.Valid(b) {
return nil, false
}
r := &jsonReader{b: b}
r.space()
if !r.at('{') {
return nil, false
}
members, ok := r.object(true)
r.space()
return members, ok && r.i == len(b)
}
// jsonReader reads JSON from b at i.
type jsonReader struct {
b []byte
i int
}
func (r *jsonReader) at(c byte) bool { return r.i < len(r.b) && r.b[r.i] == c }
// space skips the four spaces of JSON.
func (r *jsonReader) space() {
for r.i < len(r.b) && strings.IndexByte(" \t\n\r", r.b[r.i]) >= 0 {
r.i++
}
}
// value reads one value of any kind.
func (r *jsonReader) value() (jsonMember, bool) {
if r.i >= len(r.b) {
return jsonMember{}, false
}
start := r.i
m := jsonMember{kind: r.b[r.i]}
ok := false
switch c := r.b[r.i]; {
case c == '{':
_, ok = r.object(false)
case c == '[':
ok = r.array()
case c == '"':
m.str, ok = r.string()
case c == 't':
ok = r.literal("true")
case c == 'f':
ok = r.literal("false")
case c == 'n':
ok = r.literal("null")
case c == '-' || c >= '0' && c <= '9':
m.kind, ok = '0', r.number()
}
m.raw = r.b[start:r.i]
return m, ok
}
// object reads an object, with no name twice, and returns its members when
// keep is set.
func (r *jsonReader) object(keep bool) ([]jsonMember, bool) {
r.i++ // {
r.space()
if r.at('}') {
r.i++
return nil, true
}
var out []jsonMember
seen := map[string]bool{}
for {
r.space()
if !r.at('"') {
return nil, false
}
name, ok := r.string()
if !ok || seen[name] {
return nil, false
}
seen[name] = true
r.space()
if !r.at(':') {
return nil, false
}
r.i++
r.space()
m, ok := r.value()
if !ok {
return nil, false
}
if keep {
m.name = name
out = append(out, m)
}
r.space()
switch {
case r.at(','):
r.i++
case r.at('}'):
r.i++
return out, true
default:
return nil, false
}
}
}
func (r *jsonReader) array() bool {
r.i++ // [
r.space()
if r.at(']') {
r.i++
return true
}
for {
r.space()
if _, ok := r.value(); !ok {
return false
}
r.space()
switch {
case r.at(','):
r.i++
case r.at(']'):
r.i++
return true
default:
return false
}
}
}
// string reads a string and decodes its escapes; a surrogate escaped alone,
// without its pair, breaks it.
func (r *jsonReader) string() (string, bool) {
r.i++ // "
var sb strings.Builder
for r.i < len(r.b) {
c := r.b[r.i]
switch {
case c == '"':
r.i++
return sb.String(), true
case c < 0x20:
return "", false
case c != '\\':
_, n := utf8.DecodeRune(r.b[r.i:])
sb.Write(r.b[r.i : r.i+n])
r.i += n
continue
}
if r.i+1 >= len(r.b) {
return "", false
}
e := r.b[r.i+1]
r.i += 2
if k := strings.IndexByte(`"\/bfnrt`, e); k >= 0 {
sb.WriteByte("\"\\/\b\f\n\r\t"[k])
continue
}
if e != 'u' {
return "", false
}
u, ok := r.hex4()
switch {
case !ok || u >= 0xdc00 && u <= 0xdfff:
return "", false
case u >= 0xd800 && u <= 0xdbff:
if r.i+1 >= len(r.b) || r.b[r.i] != '\\' || r.b[r.i+1] != 'u' {
return "", false
}
r.i += 2
low, ok := r.hex4()
if !ok || low < 0xdc00 || low > 0xdfff {
return "", false
}
u = 0x10000 + (u-0xd800)<<10 + (low - 0xdc00)
}
sb.WriteRune(rune(u))
}
return "", false
}
// hex4 reads the four hexadecimal digits of an escape \u.
func (r *jsonReader) hex4() (int, bool) {
if r.i+4 > len(r.b) {
return 0, false
}
u := 0
for _, c := range r.b[r.i : r.i+4] {
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
}
u = u<<4 | d
}
r.i += 4
return u, true
}
func (r *jsonReader) literal(word string) bool {
if !strings.HasPrefix(string(r.b[r.i:]), word) {
return false
}
r.i += len(word)
return true
}
// number reads a number of the grammar of RFC 8259: a minus, an integer
// part without leading zeros, and an optional fraction and exponent.
func (r *jsonReader) number() bool {
digits := func() int {
n := 0
for r.i < len(r.b) && r.b[r.i] >= '0' && r.b[r.i] <= '9' {
r.i++
n++
}
return n
}
if r.at('-') {
r.i++
}
switch {
case r.at('0'):
r.i++
case digits() == 0:
return false
}
if r.at('.') {
r.i++
if digits() == 0 {
return false
}
}
if r.at('e') || r.at('E') {
r.i++
if r.at('+') || r.at('-') {
r.i++
}
if digits() == 0 {
return false
}
}
return true
}
// maxJSONRound is the largest round of drand's JSON, 2^53 - 1, as in the
// release object.
const maxJSONRound = 1<<53 - 1
// jsonRound reads the round of drand's JSON: a number without sign, fraction
// or exponent, from 1 to 2^53 - 1.
func jsonRound(m jsonMember) (uint64, bool) {
if m.kind != '0' || len(m.raw) == 0 || len(m.raw) > 16 || m.raw[0] == '0' {
return 0, false
}
var n uint64
for _, c := range m.raw {
if c < '0' || c > '9' {
return 0, false
}
n = n*10 + uint64(c-'0')
}
return n, n <= maxJSONRound
}

@ -0,0 +1,86 @@
package provider
import (
"strings"
"testing"
)
// Spec v0.16, §47.1: the strict reading of drand's JSON, at the edges of the
// grammar of RFC 8259 that release.json does not reach.
func TestStrictJSON(t *testing.T) {
for _, tc := range []struct {
in string
ok bool
}{
{`{}`, true},
{` {"a":1} `, true},
{"\t{\"a\":1}\r\n", true},
{`{"a":[]}`, true},
{`{"a":[1,2,[3,{}]]}`, true},
{`{"a":true,"b":false,"c":null}`, true},
{`{"a":-0,"b":0.5,"c":1E+2,"d":1e-2,"e":-12.25e3}`, true},
{`{"a":"\"\\\/\b\f\n\r\té"}`, true},
{`{"é":1,"é":2}`, false}, // one name, escaped and not
{`{"a":1,"a":2}`, false},
{`{"a":{"b":1},"c":{"b":2}}`, true}, // the same name in two objects
{`{"a":[{"b":1,"b":1}]}`, false},
{`{"a":01}`, false},
{`{"a":1.}`, false},
{`{"a":.5}`, false},
{`{"a":1e}`, false},
{`{"a":+1}`, false},
{`{"a":-}`, false},
{`{"a":tru}`, false},
{`{"a":nul}`, false},
{`{"a":"\x"}`, false},
{`{"a":"\u12"}`, false},
{`{"a":"\u12g4"}`, false},
{`{"a":"\ud800"}`, false},
{`{"a":"\ud800A"}`, false},
{`{"a":"\ud800x"}`, false},
{`{"a":"\udfff\ud800"}`, false},
{`{"a":"😀"}`, true},
{`{"a":"` + "\x01" + `"}`, false},
{`{"a":"` + "\xff" + `"}`, false},
{"{\"a\":\"\xed\xa0\x80\"}", false}, // a surrogate in UTF-8
{`{"a":1,}`, false},
{`{,"a":1}`, false},
{`{"a" 1}`, false},
{`{"a":1 "b":2}`, false},
{`{a:1}`, false},
{`{"a":[1,]}`, false},
{`{"a":[1 2]}`, false},
{`{"a":"b"`, false},
{`{"a":1}x`, false},
{`[]`, false},
{`"a"`, false},
{``, false},
{"{\"a\":1}\v", false}, // not a space of JSON
} {
if _, ok := strictJSON([]byte(tc.in)); ok != tc.ok {
t.Errorf("%q: %v, want %v", tc.in, ok, tc.ok)
}
}
m, ok := strictJSON([]byte(`{"round":1000,"note":"a😀","n":-1.5}`))
if !ok || len(m) != 3 || m[0].name != "round" || string(m[0].raw) != "1000" || m[1].str != "a\U0001F600" || m[2].kind != '0' || string(m[2].raw) != "-1.5" {
t.Errorf("members %+v", m)
}
}
func TestJSONRound(t *testing.T) {
for raw, want := range map[string]bool{
"1": true, "1000": true, "9007199254740991": true,
"0": false, "9007199254740992": false, "99999999999999999": false,
"-1": false, "1.0": false, "1e3": false, "01": false,
} {
if _, ok := jsonRound(jsonMember{kind: '0', raw: []byte(raw)}); ok != want {
t.Errorf("%s: %v, want %v", raw, ok, want)
}
}
if _, ok := jsonRound(jsonMember{kind: '"', raw: []byte(`"1"`)}); ok {
t.Error("a string is not a round")
}
if _, err := ParseDrandJSON([]byte(`{"round":1000,"signature":"` + strings.Repeat("ab", 48) + `","note":"` + "\xff" + `"}`)); err == nil {
t.Error("invalid UTF-8 is malformed")
}
}

@ -3,7 +3,6 @@ package provider
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
datekeys "g.activething.com/go/DateKeys"
@ -146,32 +145,59 @@ func DecodeRelease(b []byte) (Release, error) {
// ErrReleaseInvalid. It is accepted as input, never written.
func ParseRelease(b []byte) (Release, error) {
if t := bytes.TrimLeft(b, " \t\r\n"); len(t) > 0 && t[0] == '{' {
return parseDrandJSON(b)
return ParseDrandJSON(b)
}
return DecodeRelease(b)
}
// parseDrandJSON reads the JSON of a drand relay.
func parseDrandJSON(b []byte) (Release, error) {
// ParseDrandJSON reads the JSON of a drand relay with the strict rules of
// spec v0.16, §47.1: at most MaxReleaseJSONSize bytes of JSON whose value is
// an object, with no repeated name and names compared exactly once their
// escapes are decoded; "round" a number without sign, fraction or exponent,
// from 1 to 2^53 - 1; "signature" a string of hexadecimal, in lower or upper
// case; and "randomness", when present, a string with SHA-256 of the
// signature in hexadecimal. Other members are ignored. Any failure is
// ErrReleaseInvalid. provider/drand reads the answers of the relays with it.
func ParseDrandJSON(b []byte) (Release, error) {
if len(b) > MaxReleaseJSONSize {
return Release{}, fmt.Errorf("provider: drand JSON of %d bytes, larger than %d: %w", len(b), MaxReleaseJSONSize, datekeys.ErrReleaseInvalid)
}
var wire struct {
Round *uint64 `json:"round"`
Signature *string `json:"signature"`
Randomness string `json:"randomness"`
malformed := fmt.Errorf("provider: drand JSON: malformed, or without round or signature: %w", datekeys.ErrReleaseInvalid)
members, ok := strictJSON(b)
if !ok {
return Release{}, malformed
}
if err := json.Unmarshal(b, &wire); err != nil || wire.Round == nil || wire.Signature == nil {
return Release{}, fmt.Errorf("provider: drand JSON: malformed, or without round or signature: %w", datekeys.ErrReleaseInvalid)
var round uint64
var signature, randomness *string
for _, m := range members {
switch m.name {
case "round":
if round, ok = jsonRound(m); !ok {
return Release{}, malformed
}
sig, err := hex.DecodeString(*wire.Signature)
case "signature", "randomness":
if m.kind != '"' {
return Release{}, malformed
}
v := m.str
if m.name == "signature" {
signature = &v
} else {
randomness = &v
}
}
}
if round == 0 || signature == nil {
return Release{}, malformed
}
sig, err := hex.DecodeString(*signature)
if err != nil {
return Release{}, fmt.Errorf("provider: drand JSON: signature is not hex: %w", datekeys.ErrReleaseInvalid)
}
if wire.Randomness != "" && !randomnessMatches(wire.Randomness, sig) {
if randomness != nil && !randomnessMatches(*randomness, sig) {
return Release{}, fmt.Errorf("provider: drand JSON: randomness does not match the signature: %w", datekeys.ErrReleaseInvalid)
}
return Release{Round: *wire.Round, Signature: sig}, nil
return Release{Round: round, Signature: sig}, nil
}
// Supplier hands over a release that the caller has in hand (spec v0.15,

@ -358,7 +358,13 @@ one of CBOR (RFC 8949).
case, and may have `randomness`, which must then be SHA-256 of the
signature; it names no chain, so its `release` has no `chain_hash`. Any
failure to read it is `ERR_RELEASE_INVALID`, and so is one of more than
8192 bytes; then the round and the signature, as for an object.
8192 bytes; then the round and the signature, as for an object. Since
v0.16 it is read strictly: no object of the JSON repeats a name, names are
compared exactly once their escapes are decoded (`"round"` is
`round`, and `ROUND` another name, which is ignored), an escape of a lone
surrogate is malformed, `round` is a number without sign, fraction or
exponent from 1 to 2^53 − 1, and `signature` and `randomness` are strings.
The cases of v0.16 follow those of v0.15 in the list.
- `archive`: the lookups of the local archive `releases/archive_1000_1004.bin`,
an informative format (spec v0.15, §50). It is the `header`, the
deterministic CBOR map `{0: "datekeys-release-archive", 1: 1, 2: chain_hash,

@ -432,6 +432,204 @@
},
"result": "ERR_RELEASE_INVALID",
"text": "provider: the signature is not a canonical point encoding, or does not verify as the BLS signature of round 1000 under datekeys:quicknet:v1: ERR_RELEASE_INVALID"
},
{
"name": "round twice",
"input": "{\"round\":1000,\"round\":1001,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round twice, once escaped as round",
"input": "{\"round\":1000,\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round twice, the second null",
"input": "{\"round\":1000,\"round\":null,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round escaped as round",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"release": {
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
"result": "ok"
},
{
"name": "Round instead of round",
"input": "{\"Round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "ROUND beside round: another name, ignored",
"input": "{\"round\":1000,\"ROUND\":1001,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"release": {
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
"result": "ok"
},
{
"name": "round null",
"input": "{\"round\":null,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round 1000.0",
"input": "{\"round\":1000.0,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round 1e3",
"input": "{\"round\":1e3,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round -1000",
"input": "{\"round\":-1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round 0",
"input": "{\"round\":0,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round 2^53",
"input": "{\"round\":9007199254740992,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "round 2^53 - 1 for a DateKey of round 1000",
"input": "{\"round\":9007199254740991,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"release": {
"round": 9007199254740991,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
"result": "ERR_ROUND_MISMATCH",
"text": "provider: release for round 9007199254740991, expected 1000: ERR_ROUND_MISMATCH"
},
{
"name": "round with a leading zero",
"input": "{\"round\":01000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "signature twice",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "signature null",
"input": "{\"round\":1000,\"signature\":null}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "randomness empty",
"input": "{\"round\":1000,\"randomness\":\"\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: randomness does not match the signature: ERR_RELEASE_INVALID"
},
{
"name": "randomness null",
"input": "{\"round\":1000,\"randomness\":null,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "another name twice",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"note\":1,\"note\":2}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "a name twice in a nested object",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"meta\":{\"a\":1,\"a\":2}}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "nested objects and arrays, ignored",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"meta\":{\"a\":[1,{\"a\":2}],\"b\":{},\"c\":[]}}",
"round": 1000,
"release": {
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
"result": "ok"
},
{
"name": "a lone surrogate in another name",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"\\ud800\":1}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "a lone low surrogate in a value",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"note\":\"\\udc00\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "a surrogate pair in a value",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"note\":\"😀\"}",
"round": 1000,
"release": {
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
"result": "ok"
},
{
"name": "a tab inside a string",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\",\"note\":\"a\tb\"}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
},
{
"name": "something after the object",
"input": "{\"round\":1000,\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}{}",
"round": 1000,
"result": "ERR_RELEASE_INVALID",
"text": "provider: drand JSON: malformed, or without round or signature: ERR_RELEASE_INVALID"
}
],
"archive": {

Loading…
Cancel
Save

Powered by TurnKey Linux.