]> git.ozlabs.org Git - ccan/commitdiff
rune: new module to implement runes.
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 23 Jun 2022 04:37:37 +0000 (14:07 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 23 Jun 2022 04:37:37 +0000 (14:07 +0930)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ccan/rune/LICENSE [new symlink]
ccan/rune/_info [new file with mode: 0644]
ccan/rune/coding.c [new file with mode: 0644]
ccan/rune/internal.h [new file with mode: 0644]
ccan/rune/rune.c [new file with mode: 0644]
ccan/rune/rune.h [new file with mode: 0644]
ccan/rune/test/run.c [new file with mode: 0644]
ccan/rune/test/test_vectors.csv [new file with mode: 0644]

diff --git a/ccan/rune/LICENSE b/ccan/rune/LICENSE
new file mode 120000 (symlink)
index 0000000..2354d12
--- /dev/null
@@ -0,0 +1 @@
+../../licenses/BSD-MIT
\ No newline at end of file
diff --git a/ccan/rune/_info b/ccan/rune/_info
new file mode 100644 (file)
index 0000000..e360156
--- /dev/null
@@ -0,0 +1,131 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * rune - Simple cookies you can extend (a-la Python runes class).
+ *
+ * This code is a form of cookies, but they are user-extensible, and
+ * contain a simple language to define what the cookie allows.
+ *
+ * A "rune" contains the hash of a secret (so the server can
+ * validate), such that you can add, but not subtract, conditions.
+ * This is a simplified form of Macaroons, See
+ * https://research.google/pubs/pub41892/ "Macaroons: Cookies with
+ * Contextual Caveats for Decentralized Authorization in the Cloud".
+ * It has one good idea, some extended ideas nobody implements, and
+ * lots and lots of words.
+ *
+ * License: BSD-MIT
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ * Example:
+ * // Given "generate secret 1" outputs kr7AW-eJ2Munhv5ftu4rHqAnhxUpPQM8aOyWOmqiytk9MQ==
+ * // Given "add kr7AW-eJ2Munhv5ftu4rHqAnhxUpPQM8aOyWOmqiytk9MQ== uid=rusty" outputs Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk=
+ * // Given "test secret Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk= rusty" outputs PASSED
+ * // Given "test secret Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk= notrusty" outputs FAILED: uid is not equal to rusty
+ * // Given "add Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk= t\<1655958616" outputs _YBFmeAedqlLigWHAmvyyGGHRrnI40BRQGh2hWdSZ9E9MSZ1aWQ9cnVzdHkmdDwxNjU1OTU4NjE2
+ * // Given "test secret _YBFmeAedqlLigWHAmvyyGGHRrnI40BRQGh2hWdSZ9E9MSZ1aWQ9cnVzdHkmdDwxNjU1OTU4NjE2 rusty" outputs FAILED: t is greater or equal to 1655958616
+ * #include <ccan/err/err.h>
+ * #include <ccan/rune/rune.h>
+ * #include <ccan/str/str.h>
+ * #include <stdio.h>
+ * #include <sys/time.h>
+ * 
+ * // We support two values: current time (t), and user id (uid).
+ * static const char *check(const tal_t *ctx,
+ *                         const struct rune *rune,
+ *                         const struct rune_altern *alt,
+ *                         char *uid)
+ * {
+ *     // t= means current time, in seconds, as integer
+ *     if (streq(alt->fieldname, "t")) {
+ *             struct timeval now;
+ *             s64 t;
+ *             gettimeofday(&now, NULL);
+ *             t = now.tv_sec;
+ *             return rune_alt_single(ctx, alt, NULL, &t);
+ *     }
+ *     if (streq(alt->fieldname, "uid")) {
+ *             return rune_alt_single(ctx, alt, uid, NULL);
+ *     }
+ *     // Otherwise, field is missing
+ *     return rune_alt_single(ctx, alt, NULL, NULL);
+ * }
+ * 
+ * int main(int argc, char *argv[])
+ * {
+ *     struct rune *master, *rune;
+ * 
+ *     if (argc < 3)
+ *             goto usage;
+ * 
+ *     if (streq(argv[1], "generate")) {
+ *             // Make master, derive a unique_id'd rune.
+ *             if (argc != 3 && argc != 4)
+ *                     goto usage;
+ *             master = rune_new(NULL, (u8 *)argv[2], strlen(argv[2]), NULL);
+ *             rune = rune_derive_start(NULL, master, argv[3]);
+ *     } else if (streq(argv[1], "add")) {
+ *             // Add a restriction
+ *             struct rune_restr *restr;
+ *             if (argc != 4)
+ *                     goto usage;
+ *             rune = rune_from_base64(NULL, argv[2]);
+ *             if (!rune)
+ *                     errx(1, "Bad rune");
+ *             restr = rune_restr_from_string(NULL, argv[3]);
+ *             if (!restr)
+ *                     errx(1, "Bad restriction string");
+ *             rune_add_restr(rune, restr);
+ *     } else if (streq(argv[1], "test")) {
+ *             const char *err;
+ *             if (argc != 5)
+ *                     goto usage;
+ *             master = rune_new(NULL, (u8 *)argv[2], strlen(argv[2]), NULL);
+ *             if (!master)
+ *                     errx(1, "Bad master rune");
+ *             rune = rune_from_base64(NULL, argv[3]);
+ *             if (!rune)
+ *                     errx(1, "Bad rune");
+ *             err = rune_test(NULL, master, rune, check, argv[4]);
+ *             if (err)
+ *                     printf("FAILED: %s\n", err);
+ *             else
+ *                     printf("PASSED\n");
+ *             return 0;
+ *     } else
+ *             goto usage;
+ * 
+ *     printf("%s\n", rune_to_base64(NULL, rune));
+ *     return 0;
+ * 
+ * usage:
+ *     errx(1, "Usage: %s generate <secret> <uniqueid> OR\n"
+ *          "%s add <rune> <restriction> OR\n"
+ *          "%s test <secret> <rune> <uid>", argv[0], argv[0], argv[0]);
+ * }
+ */
+int main(int argc, char *argv[])
+{
+       /* Expect exactly one argument */
+       if (argc != 2)
+               return 1;
+
+       if (strcmp(argv[1], "depends") == 0) {
+               printf("ccan/base64\n");
+               printf("ccan/crypto/sha256\n");
+               printf("ccan/endian\n");
+               printf("ccan/short_types\n");
+               printf("ccan/str/hex\n");
+               printf("ccan/tal/str\n");
+               printf("ccan/tal\n");
+               printf("ccan/typesafe_cb\n");
+               return 0;
+       }
+       if (strcmp(argv[1], "testdepends") == 0) {
+               printf("ccan/tal/grab_file\n");
+               return 0;
+       }
+
+       return 1;
+}
diff --git a/ccan/rune/coding.c b/ccan/rune/coding.c
new file mode 100644 (file)
index 0000000..201553d
--- /dev/null
@@ -0,0 +1,417 @@
+/* MIT (BSD) license - see LICENSE file for details */
+/* Routines to encode / decode a rune */
+#include <ccan/rune/rune.h>
+#include <ccan/rune/internal.h>
+#include <ccan/str/hex/hex.h>
+#include <ccan/tal/str/str.h>
+#include <ccan/base64/base64.h>
+#include <ccan/endian/endian.h>
+#include <errno.h>
+
+/* From Python base64.urlsafe_b64encode:
+ *
+ * The alphabet uses '-' instead of '+' and '_' instead of '/'.
+ */
+static const base64_maps_t base64_maps_urlsafe = {
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
+
+  "\xff\xff\xff\xff\xff" /* 0 */
+  "\xff\xff\xff\xff\xff" /* 5 */
+  "\xff\xff\xff\xff\xff" /* 10 */
+  "\xff\xff\xff\xff\xff" /* 15 */
+  "\xff\xff\xff\xff\xff" /* 20 */
+  "\xff\xff\xff\xff\xff" /* 25 */
+  "\xff\xff\xff\xff\xff" /* 30 */
+  "\xff\xff\xff\xff\xff" /* 35 */
+  "\xff\xff\xff\xff\xff" /* 40 */
+  "\x3e\xff\xff\x34\x35" /* 45 */
+  "\x36\x37\x38\x39\x3a" /* 50 */
+  "\x3b\x3c\x3d\xff\xff" /* 55 */
+  "\xff\xff\xff\xff\xff" /* 60 */
+  "\x00\x01\x02\x03\x04" /* 65 A */
+  "\x05\x06\x07\x08\x09" /* 70 */
+  "\x0a\x0b\x0c\x0d\x0e" /* 75 */
+  "\x0f\x10\x11\x12\x13" /* 80 */
+  "\x14\x15\x16\x17\x18" /* 85 */
+  "\x19\xff\xff\xff\xff" /* 90 */
+  "\x3f\xff\x1a\x1b\x1c" /* 95 */
+  "\x1d\x1e\x1f\x20\x21" /* 100 */
+  "\x22\x23\x24\x25\x26" /* 105 */
+  "\x27\x28\x29\x2a\x2b" /* 110 */
+  "\x2c\x2d\x2e\x2f\x30" /* 115 */
+  "\x31\x32\x33\xff\xff" /* 120 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 125 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 135 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 145 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 155 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 165 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 175 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 185 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 195 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 205 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 215 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 225 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 235 */
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 245 */
+};
+
+/* For encoding as a string */
+struct wbuf {
+       size_t off, len;
+       char *buf;
+};
+
+static void to_wbuf(const char *s, size_t len, void *vwbuf)
+{
+       struct wbuf *wbuf = vwbuf;
+
+       while (wbuf->off + len > wbuf->len)
+               tal_resize(&wbuf->buf, wbuf->len *= 2);
+       memcpy(wbuf->buf + wbuf->off, s, len);
+       wbuf->off += len;
+}
+
+/* For adding to sha256 */
+static void to_sha256(const char *s, size_t len, void *vshactx)
+{
+       struct sha256_ctx *shactx = vshactx;
+       sha256_update(shactx, s, len);
+}
+
+static void rune_altern_encode(const struct rune_altern *altern,
+                              void (*cb)(const char *s, size_t len,
+                                         void *arg),
+                              void *arg)
+{
+       char cond = altern->condition;
+       const char *p;
+
+       cb(altern->fieldname, strlen(altern->fieldname), arg);
+       cb(&cond, 1, arg);
+
+       p = altern->value;
+       for (;;) {
+               char esc[2] = { '\\' };
+               size_t len = strcspn(p, "\\|&");
+               cb(p, len, arg);
+               if (!p[len])
+                       break;
+               esc[1] = p[len];
+               cb(esc, 2, arg);
+               p++;
+       }
+}
+
+static void rune_restr_encode(const struct rune_restr *restr,
+                             void (*cb)(const char *s, size_t len,
+                                        void *arg),
+                             void *arg)
+{
+       for (size_t i = 0; i < tal_count(restr->alterns); i++) {
+               if (i != 0)
+                       cb("|", 1, arg);
+               rune_altern_encode(restr->alterns[i], cb, arg);
+       }
+}
+
+void rune_sha256_add_restr(struct sha256_ctx *shactx,
+                          struct rune_restr *restr)
+{
+       rune_restr_encode(restr, to_sha256, shactx);
+       rune_sha256_endmarker(shactx);
+}
+
+const char *rune_is_derived(const struct rune *source, const struct rune *rune)
+{
+       if (!runestr_eq(source->version, rune->version))
+               return "Version mismatch";
+
+       return rune_is_derived_anyversion(source, rune);
+}
+       
+const char *rune_is_derived_anyversion(const struct rune *source,
+                                      const struct rune *rune)
+{
+       struct sha256_ctx shactx;
+       size_t i;
+
+       if (tal_count(rune->restrs) < tal_count(source->restrs))
+               return "Fewer restrictions than master";
+
+       /* If we add the same restrictions to source rune, do we match? */
+       shactx = source->shactx;
+       for (i = 0; i < tal_count(rune->restrs); i++) {
+               /* First restrictions must be identical */
+               if (i < tal_count(source->restrs)) {
+                       if (!rune_restr_eq(source->restrs[i], rune->restrs[i]))
+                               return "Does not match master restrictions";
+               } else
+                       rune_sha256_add_restr(&shactx, rune->restrs[i]);
+       }
+
+       if (memcmp(shactx.s, rune->shactx.s, sizeof(shactx.s)) != 0)
+               return "Not derived from master";
+       return NULL;
+}
+
+static bool peek_char(const char *data, size_t len, char *c)
+{
+       if (len == 0)
+               return false;
+       *c = *data;
+       return true;
+}
+
+static void drop_char(const char **data, size_t *len)
+{
+       (*data)++;
+       (*len)--;
+}
+
+static void pull_invalid(const char **data, size_t *len)
+{
+       *data = NULL;
+       *len = 0;
+}
+
+static bool pull_char(const char **data, size_t *len, char *c)
+{
+       if (!peek_char(*data, *len, c)) {
+               pull_invalid(data, len);
+               return false;
+       }
+       drop_char(data, len);
+       return true;
+}
+
+static bool is_valid_cond(enum rune_condition cond)
+{
+       switch (cond) {
+       case RUNE_COND_IF_MISSING:
+       case RUNE_COND_EQUAL:
+       case RUNE_COND_NOT_EQUAL:
+       case RUNE_COND_BEGINS:
+       case RUNE_COND_ENDS:
+       case RUNE_COND_CONTAINS:
+       case RUNE_COND_INT_LESS:
+       case RUNE_COND_INT_GREATER:
+       case RUNE_COND_LEXO_BEFORE:
+       case RUNE_COND_LEXO_AFTER:
+       case RUNE_COND_COMMENT:
+               return true;
+       }
+       return false;
+}
+
+/* Sets *more on success: true if another altern follows */
+static struct rune_altern *rune_altern_decode(const tal_t *ctx,
+                                             const char **data, size_t *len,
+                                             bool *more)
+{
+       struct rune_altern *alt = tal(ctx, struct rune_altern);
+       const char *strstart = *data;
+       char *value;
+       size_t strlen = 0;
+       char c;
+
+        /* Swallow field up to conditional */
+       for (;;) {
+               if (!pull_char(data, len, &c))
+                       return tal_free(alt);
+               if (cispunct(c))
+                       break;
+               strlen++;
+       }
+
+       alt->fieldname = tal_strndup(alt, strstart, strlen);
+       if (!is_valid_cond(c)) {
+               pull_invalid(data, len);
+               return tal_free(alt);
+       }
+       alt->condition = c;
+
+       /* Assign worst case. */
+       value = tal_arr(alt, char, *len + 1);
+       strlen = 0;
+       *more = false;
+       while (*len && pull_char(data, len, &c)) {
+               if (c == '|') {
+                       *more = true;
+                       break;
+               }
+               if (c == '&')
+                       break;
+
+               if (c == '\\' && !pull_char(data, len, &c))
+                       return tal_free(alt);
+               value[strlen++] = c;
+       }
+       value[strlen] = '\0';
+       tal_resize(&value, strlen + 1);
+       alt->value = value;
+       return alt;
+}
+
+static struct rune_restr *rune_restr_decode(const tal_t *ctx,
+                                           const char **data, size_t *len)
+{
+       struct rune_restr *restr = tal(ctx, struct rune_restr);
+       size_t num_alts = 0;
+       bool more;
+
+       /* Must have at least one! */
+       restr->alterns = tal_arr(restr, struct rune_altern *, 0);
+       do {
+               struct rune_altern *alt;
+
+               alt = rune_altern_decode(restr, data, len, &more);
+               if (!alt)
+                       return tal_free(restr);
+               tal_resize(&restr->alterns, num_alts+1);
+               restr->alterns[num_alts++] = alt;
+       } while (more);
+       return restr;
+}
+
+static struct rune *from_string(const tal_t *ctx,
+                               const char *str,
+                               const u8 *hash32)
+{
+       size_t len = strlen(str);
+       struct rune *rune = tal(ctx, struct rune);
+
+       /* Now count up how many bytes we should have hashed: secret uses
+        * first block. */
+       rune->shactx.bytes = 64;
+
+       rune->restrs = tal_arr(rune, struct rune_restr *, 0);
+       rune->unique_id = NULL;
+       rune->version = NULL;
+
+       while (len) {
+               struct rune_restr *restr;
+               restr = rune_restr_decode(rune, &str, &len);
+               if (!restr)
+                       return tal_free(rune);
+               if (!rune_add_restr(rune, restr))
+                       return tal_free(rune);
+       }
+
+       /* Now we replace with canned hash state */
+       memcpy(rune->shactx.s, hash32, 32);
+       for (size_t i = 0; i < 8; i++)
+               rune->shactx.s[i] = be32_to_cpu(rune->shactx.s[i]);
+
+       return rune;
+}
+
+struct rune_restr *rune_restr_from_string(const tal_t *ctx,
+                                         const char *str)
+{
+       size_t len = strlen(str);
+       struct rune_restr *restr;
+
+       restr = rune_restr_decode(NULL, &str, &len);
+       /* Don't allow trailing chars */
+       if (restr && len != 0)
+               restr = tal_free(restr);
+       return tal_steal(ctx, restr);
+}
+
+static void to_string(struct wbuf *wbuf, const struct rune *rune, u8 *hash32)
+{
+       /* Copy hash in big-endian */
+       for (size_t i = 0; i < 8; i++) {
+               be32 v = cpu_to_be32(rune->shactx.s[i]);
+               memcpy(hash32 + i*4, &v, sizeof(v));
+       }
+
+       for (size_t i = 0; i < tal_count(rune->restrs); i++) {
+               if (i != 0)
+                       to_wbuf("&", 1, wbuf);
+               rune_restr_encode(rune->restrs[i], to_wbuf, wbuf);
+       }
+       to_wbuf("", 1, wbuf);
+}
+
+struct rune *rune_from_base64(const tal_t *ctx, const char *str)
+{
+       size_t len;
+       u8 *data;
+       struct rune *rune;
+
+       data = tal_arr(NULL, u8, base64_decoded_length(strlen(str)) + 1);
+
+       len = base64_decode_using_maps(&base64_maps_urlsafe,
+                                      (char *)data, tal_bytelen(data),
+                                      str, strlen(str));
+       if (len == -1)
+               goto fail;
+                       
+       if (len < 32)
+               goto fail;
+
+       data[len] = '\0';
+       /* Sanity check that it's a valid string! */
+       if (strlen((char *)data + 32) != len - 32)
+               goto fail;
+
+       rune = from_string(ctx, (const char *)data + 32, data);
+       tal_free(data);
+       return rune;
+
+fail:
+       tal_free(data);
+       return NULL;
+}
+
+char *rune_to_base64(const tal_t *ctx, const struct rune *rune)
+{
+       u8 hash32[32];
+       char *ret;
+       size_t ret_len;
+       struct wbuf wbuf;
+
+       /* We're going to prepend hash */
+       wbuf.off = sizeof(hash32);
+       wbuf.len = 64;
+       wbuf.buf = tal_arr(NULL, char, wbuf.len);
+
+       to_string(&wbuf, rune, hash32);
+       /* Prepend hash */
+       memcpy(wbuf.buf, hash32, sizeof(hash32));
+
+       ret = tal_arr(ctx, char, base64_encoded_length(wbuf.off) + 1);
+       ret_len = base64_encode_using_maps(&base64_maps_urlsafe,
+                                          ret, tal_bytelen(ret),
+                                          wbuf.buf, wbuf.off - 1);
+       ret[ret_len] = '\0';
+       tal_free(wbuf.buf);
+       return ret;
+}
+
+struct rune *rune_from_string(const tal_t *ctx, const char *str)
+{
+       u8 hash[32];
+       if (!hex_decode(str, 64, hash, sizeof(hash)))
+               return NULL;
+       if (str[64] != ':')
+               return NULL;
+       return from_string(ctx, str + 65, hash);
+}
+
+char *rune_to_string(const tal_t *ctx, const struct rune *rune)
+{
+       u8 hash32[32];
+       struct wbuf wbuf;
+
+       /* We're going to prepend hash (in hex), plus colon */
+       wbuf.off = sizeof(hash32) * 2 + 1;
+       wbuf.len = 128;
+       wbuf.buf = tal_arr(ctx, char, wbuf.len);
+
+       to_string(&wbuf, rune, hash32);
+       hex_encode(hash32, sizeof(hash32), wbuf.buf, sizeof(hash32) * 2 + 1);
+       wbuf.buf[sizeof(hash32) * 2] = ':';
+       return wbuf.buf;
+}
diff --git a/ccan/rune/internal.h b/ccan/rune/internal.h
new file mode 100644 (file)
index 0000000..e4de06c
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef CCAN_RUNE_INTERNAL_H
+#define CCAN_RUNE_INTERNAL_H
+/* MIT (BSD) license - see LICENSE file for details */
+void rune_sha256_endmarker(struct sha256_ctx *shactx);
+void rune_sha256_add_restr(struct sha256_ctx *shactx,
+                       struct rune_restr *restr);
+bool runestr_eq(const char *a, const char *b);
+#endif /* CCAN_RUNE_INTERNAL_H */
diff --git a/ccan/rune/rune.c b/ccan/rune/rune.c
new file mode 100644 (file)
index 0000000..36eaab9
--- /dev/null
@@ -0,0 +1,454 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#include "config.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <ccan/endian/endian.h>
+#include <ccan/tal/str/str.h>
+#include <ccan/rune/rune.h>
+#include <ccan/rune/internal.h>
+
+/* Helper to produce an id field */
+static struct rune_restr *unique_id_restr(const tal_t *ctx,
+                                         const char *unique_id,
+                                         const char *version)
+{
+       const char *id;
+       struct rune_restr *restr;
+
+       assert(!strchr(unique_id, '-'));
+       if (version)
+               id = tal_fmt(NULL, "%s-%s", unique_id, version);
+       else
+               id = tal_strdup(NULL, unique_id);
+
+       restr = rune_restr_new(ctx);
+        /* We use the empty field for this, since it's always present. */
+       rune_restr_add_altern(restr,
+                             take(rune_altern_new(NULL, "", '=', take(id))));
+       return restr;
+}
+
+/* We pad between fields with something identical to the SHA end marker */
+void rune_sha256_endmarker(struct sha256_ctx *shactx)
+{
+       static const unsigned char pad[64] = {0x80};
+       be64 sizedesc;
+
+       sizedesc = cpu_to_be64((uint64_t)shactx->bytes << 3);
+       /* Add '1' bit to terminate, then all 0 bits, up to next block - 8. */
+       sha256_update(shactx, pad, 1 + ((128 - 8 - (shactx->bytes % 64) - 1) % 64));
+       /* Add number of bits of data (big endian) */
+       sha256_update(shactx, &sizedesc, 8);
+}
+
+struct rune *rune_new(const tal_t *ctx, const u8 *secret, size_t secret_len,
+                     const char *version)
+{
+       struct rune *rune = tal(ctx, struct rune);
+        assert(secret_len + 1 + 8 <= 64);
+
+       if (version)
+               rune->version = tal_strdup(rune, version);
+       else
+               rune->version = NULL;
+       rune->unique_id = NULL;
+       sha256_init(&rune->shactx);
+       sha256_update(&rune->shactx, secret, secret_len);
+       rune_sha256_endmarker(&rune->shactx);
+       rune->restrs = tal_arr(rune, struct rune_restr *, 0);
+       return rune;
+}
+
+struct rune *rune_dup(const tal_t *ctx, const struct rune *rune TAKES)
+{
+       struct rune *dup;
+
+       if (taken(rune))
+               return (struct rune *)rune;
+
+       dup = tal_dup(ctx, struct rune, rune);
+       dup->restrs = tal_arr(dup, struct rune_restr *, tal_count(rune->restrs));
+       for (size_t i = 0; i < tal_count(rune->restrs); i++) {
+               dup->restrs[i] = rune_restr_dup(dup->restrs,
+                                               rune->restrs[i]);
+       }
+       return dup;
+}
+
+struct rune *rune_derive_start(const tal_t *ctx,
+                              const struct rune *master,
+                              const char *unique_id TAKES)
+{
+       struct rune *rune = rune_dup(ctx, master);
+
+        /* If they provide a unique_id, it goes first. */
+       if (unique_id) {
+               if (taken(unique_id))
+                       rune->unique_id = unique_id;
+               else
+                       rune->unique_id = tal_strdup(rune, unique_id);
+               
+               rune_add_restr(rune, take(unique_id_restr(NULL,
+                                                         rune->unique_id,
+                                                         rune->version)));
+       } else {
+               assert(!rune->version);
+       }
+       return rune;
+}
+
+struct rune_altern *rune_altern_new(const tal_t *ctx,
+                                   const char *fieldname TAKES,
+                                   enum rune_condition condition,
+                                   const char *value TAKES)
+{
+       struct rune_altern *altern = tal(ctx, struct rune_altern);
+       altern->condition = condition;
+       altern->fieldname = tal_strdup(altern, fieldname);
+       altern->value = tal_strdup(altern, value);
+       return altern;
+}
+
+struct rune_altern *rune_altern_dup(const tal_t *ctx,
+                                   const struct rune_altern *altern TAKES)
+{
+       struct rune_altern *dup;
+
+       if (taken(altern))
+               return (struct rune_altern *)altern;;
+       dup = tal(ctx, struct rune_altern);
+       dup->condition = altern->condition;
+       dup->fieldname = tal_strdup(dup, altern->fieldname);
+       dup->value = tal_strdup(dup, altern->value);
+       return dup;
+}
+
+struct rune_restr *rune_restr_dup(const tal_t *ctx,
+                                 const struct rune_restr *restr TAKES)
+{
+       struct rune_restr *dup;
+       size_t num_altern;
+
+       if (taken(restr))
+               return (struct rune_restr *)restr;
+
+       num_altern = tal_count(restr->alterns);
+       dup = tal(ctx, struct rune_restr);
+       dup->alterns = tal_arr(dup, struct rune_altern *, num_altern);
+       for (size_t i = 0; i < num_altern; i++) {
+               dup->alterns[i] = rune_altern_dup(dup->alterns,
+                                                 restr->alterns[i]);
+       }
+       return dup;
+}
+
+struct rune_restr *rune_restr_new(const tal_t *ctx)
+{
+       struct rune_restr *restr = tal(ctx, struct rune_restr);
+       restr->alterns = tal_arr(restr, struct rune_altern *, 0);
+       return restr;
+}
+
+void rune_restr_add_altern(struct rune_restr *restr,
+                          const struct rune_altern *alt TAKES)
+{
+       size_t num = tal_count(restr->alterns);
+
+       tal_resize(&restr->alterns, num+1);
+       restr->alterns[num] = rune_altern_dup(restr->alterns, alt);
+}
+
+static bool is_unique_id(const struct rune_altern *alt)
+{
+       return streq(alt->fieldname, "");
+}
+       
+/* Return unique_id if valid, and sets *version */
+static const char *extract_unique_id(const tal_t *ctx,
+                                    const struct rune_altern *alt,
+                                    const char **version)
+{
+       size_t len;
+       /* Condition must be '='! */
+       if (alt->condition != '=')
+               return NULL;
+
+       len = strcspn(alt->value, "-");
+       if (alt->value[len])
+               *version = tal_strdup(ctx, alt->value + len + 1);
+       else
+               *version = NULL;
+       return tal_strndup(ctx, alt->value, len);
+}
+
+bool rune_add_restr(struct rune *rune,
+                   const struct rune_restr *restr TAKES)
+{
+       size_t num = tal_count(rune->restrs);
+
+       /* An empty fieldname is additional correctness checks */
+       for (size_t i = 0; i < tal_count(restr->alterns); i++) {
+               if (!is_unique_id(restr->alterns[i]))
+                       continue;
+
+               /* Must be the only alternative */
+               if (tal_count(restr->alterns) != 1)
+                       goto fail;
+               /* Must be the first restriction */
+               if (num != 0)
+                       goto fail;
+
+               rune->unique_id = extract_unique_id(rune,
+                                                   restr->alterns[i],
+                                                   &rune->version);
+               if (!rune->unique_id)
+                       goto fail;
+       }
+
+       tal_resize(&rune->restrs, num+1);
+       rune->restrs[num] = rune_restr_dup(rune->restrs, restr);
+
+       rune_sha256_add_restr(&rune->shactx, rune->restrs[num]);
+       return true;
+
+fail:
+       if (taken(restr))
+               tal_free(restr);
+       return false;
+}
+
+static const char *rune_restr_test(const tal_t *ctx,
+                                  const struct rune *rune,
+                                  const struct rune_restr *restr,
+                                  const char *(*check)(const tal_t *ctx,
+                                                       const struct rune *rune,
+                                                       const struct rune_altern *alt,
+                                                       void *arg),
+                                  void *arg)
+{
+       size_t num = tal_count(restr->alterns);
+       const char **errs = tal_arr(NULL, const char *, num);
+       char *err;
+
+       /* Only one alternative has to pass! */
+       for (size_t i = 0; i < num; i++) {
+               errs[i] = check(errs, rune, restr->alterns[i], arg);
+               if (!errs[i]) {
+                       tal_free(errs);
+                       return NULL;
+               }
+       }
+
+       err = tal_fmt(ctx, "%s", errs[0]);
+       for (size_t i = 1; i < num; i++)
+               tal_append_fmt(&err, " AND %s", errs[i]);
+       tal_free(errs);
+       return err;
+}
+
+static const char *cond_test(const tal_t *ctx,
+                            const struct rune_altern *alt,
+                            const char *complaint,
+                            bool cond)
+{
+       if (cond)
+               return NULL;
+
+       return tal_fmt(ctx, "%s %s %s", alt->fieldname, complaint, alt->value);
+}
+
+static const char *integer_compare_valid(const tal_t *ctx,
+                                        const s64 *fieldval_int,
+                                        const struct rune_altern *alt,
+                                        s64 *runeval_int)
+{
+       long l;
+       char *p;
+
+       if (!fieldval_int)
+               return tal_fmt(ctx, "%s is not an integer field",
+                              alt->fieldname);
+
+       errno = 0;
+       l = strtol(alt->value, &p, 10);
+       if (p == alt->value
+           || *p
+           || ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE))
+               return tal_fmt(ctx, "%s is not a valid integer", alt->value);
+
+       *runeval_int = l;
+       return NULL;
+}
+
+const char *rune_alt_single(const tal_t *ctx,
+                           const struct rune_altern *alt,
+                           const char *fieldval_str,
+                           const s64 *fieldval_int)
+{
+       char strfield[STR_MAX_CHARS(s64) + 1];
+       s64 runeval_int;
+       const char *err;
+       
+       /* Caller can't set both! */
+       if (fieldval_int) {
+               assert(!fieldval_str);
+               sprintf(strfield, "%"PRIi64, *fieldval_int);
+               fieldval_str = strfield;
+       }
+
+       switch (alt->condition) {
+       case RUNE_COND_IF_MISSING:
+               if (!fieldval_str)
+                       return NULL;
+               return tal_fmt(ctx, "%s is present", alt->fieldname);
+       case RUNE_COND_EQUAL:
+               if (!fieldval_str)
+                       return tal_fmt(ctx, "%s not present", alt->fieldname);
+               return cond_test(ctx, alt, "is not equal to",
+                                streq(fieldval_str, alt->value));
+       case RUNE_COND_NOT_EQUAL:
+               if (!fieldval_str)
+                       return tal_fmt(ctx, "%s not present", alt->fieldname);
+               return cond_test(ctx, alt, "is equal to",
+                                !streq(fieldval_str, alt->value));
+       case RUNE_COND_BEGINS:
+               if (!fieldval_str)
+                       return tal_fmt(ctx, "%s not present", alt->fieldname);
+               return cond_test(ctx, alt, "does not start with",
+                                strstarts(fieldval_str, alt->value));
+       case RUNE_COND_ENDS:
+               if (!fieldval_str)
+                       return tal_fmt(ctx, "%s not present", alt->fieldname);
+               return cond_test(ctx, alt, "does not end with",
+                                strends(fieldval_str, alt->value));
+       case RUNE_COND_CONTAINS:
+               if (!fieldval_str)
+                       return tal_fmt(ctx, "%s not present", alt->fieldname);
+               return cond_test(ctx, alt, "does not contain",
+                                strstr(fieldval_str, alt->value));
+       case RUNE_COND_INT_LESS:
+               err = integer_compare_valid(ctx, fieldval_int,
+                                           alt, &runeval_int);
+               if (err)
+                       return err;
+               return cond_test(ctx, alt, "is greater or equal to",
+                                *fieldval_int < runeval_int);
+       case RUNE_COND_INT_GREATER:
+               err = integer_compare_valid(ctx, fieldval_int,
+                                           alt, &runeval_int);
+               if (err)
+                       return err;
+               return cond_test(ctx, alt, "is less or equal to",
+                                *fieldval_int > runeval_int);
+       case RUNE_COND_LEXO_BEFORE:
+               if (!fieldval_str)
+                       return tal_fmt(ctx, "%s not present", alt->fieldname);
+               return cond_test(ctx, alt, "is equal to or ordered after",
+                                strcmp(fieldval_str, alt->value) < 0);
+       case RUNE_COND_LEXO_AFTER:
+               if (!fieldval_str)
+                       return tal_fmt(ctx, "%s not present", alt->fieldname);
+               return cond_test(ctx, alt, "is equal to or ordered before",
+                                strcmp(fieldval_str, alt->value) > 0);
+       case RUNE_COND_COMMENT:
+               return NULL;
+       }
+       /* We should never create any other values! */
+       abort();
+}
+
+const char *rune_meets_criteria_(const tal_t *ctx,
+                                const struct rune *rune,
+                                const char *(*check)(const tal_t *ctx,
+                                                     const struct rune *rune,
+                                                     const struct rune_altern *alt,
+                                                     void *arg),
+                                void *arg)
+{
+       for (size_t i = 0; i < tal_count(rune->restrs); i++) {
+               const char *err;
+
+               /* Don't "check" unique id */
+               if (i == 0 && is_unique_id(rune->restrs[i]->alterns[0]))
+                       continue;
+               
+               err = rune_restr_test(ctx, rune, rune->restrs[i], check, arg);
+               if (err)
+                       return err;
+       }
+       return NULL;
+}
+
+const char *rune_test_(const tal_t *ctx,
+                      const struct rune *master,
+                      const struct rune *rune,
+                      const char *(*check)(const tal_t *ctx,
+                                           const struct rune *rune,
+                                           const struct rune_altern *alt,
+                                           void *arg),
+                      void *arg)
+{
+       const char *err;
+
+       err = rune_is_derived(master, rune);
+       if (err)
+               return err;
+       return rune_meets_criteria_(ctx, rune, check, arg);
+}
+
+bool rune_altern_eq(const struct rune_altern *alt1,
+                   const struct rune_altern *alt2)
+{
+       return alt1->condition == alt2->condition
+               && streq(alt1->fieldname, alt2->fieldname)
+               && streq(alt1->value, alt2->value);
+}
+
+bool rune_restr_eq(const struct rune_restr *rest1,
+                  const struct rune_restr *rest2)
+{
+       if (tal_count(rest1->alterns) != tal_count(rest2->alterns))
+               return false;
+
+       for (size_t i = 0; i < tal_count(rest1->alterns); i++)
+               if (!rune_altern_eq(rest1->alterns[i], rest2->alterns[i]))
+                       return false;
+       return true;
+}
+
+/* Equal, as in both NULL, or both non-NULL and matching */
+bool runestr_eq(const char *a, const char *b)
+{
+       if (a) {
+               if (!b)
+                       return false;
+               return streq(a, b);
+       } else
+               return b == NULL;
+}
+
+bool rune_eq(const struct rune *rune1, const struct rune *rune2)
+{
+       if (!runestr_eq(rune1->unique_id, rune2->unique_id))
+               return false;
+       if (!runestr_eq(rune1->version, rune2->version))
+               return false;
+
+       if (memcmp(rune1->shactx.s, rune2->shactx.s, sizeof(rune1->shactx.s)))
+               return false;
+       if (rune1->shactx.bytes != rune2->shactx.bytes)
+               return false;
+       if (memcmp(rune1->shactx.buf.u8, rune2->shactx.buf.u8,
+                  rune1->shactx.bytes % 64))
+               return false;
+
+       if (tal_count(rune1->restrs) != tal_count(rune2->restrs))
+               return false;
+
+       for (size_t i = 0; i < tal_count(rune1->restrs); i++)
+               if (!rune_restr_eq(rune1->restrs[i], rune2->restrs[i]))
+                       return false;
+       return true;
+}
diff --git a/ccan/rune/rune.h b/ccan/rune/rune.h
new file mode 100644 (file)
index 0000000..a284b73
--- /dev/null
@@ -0,0 +1,349 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#ifndef CCAN_RUNE_RUNE_H
+#define CCAN_RUNE_RUNE_H
+#include <ccan/crypto/sha256/sha256.h>
+#include <ccan/typesafe_cb/typesafe_cb.h>
+#include <ccan/tal/tal.h>
+#include <ccan/short_types/short_types.h>
+
+/* A rune is a series of restrictions. */
+struct rune {
+       /* unique_id (if any) */
+       const char *unique_id;
+       /* Version (if any) */
+       const char *version;
+
+       /* SHA-2 256 of restrictions so far. */
+       struct sha256_ctx shactx;
+       /* Length given by tal_count() */
+       struct rune_restr **restrs;
+};
+
+/* A restriction is one or more alternatives (altern) */
+struct rune_restr {
+       /* Length given by tal_count() */
+       struct rune_altern **alterns;
+};
+
+enum rune_condition {
+       RUNE_COND_IF_MISSING = '!',
+       RUNE_COND_EQUAL = '=',
+       RUNE_COND_NOT_EQUAL = '/',
+       RUNE_COND_BEGINS = '^',
+       RUNE_COND_ENDS = '$',
+       RUNE_COND_CONTAINS = '~',
+       RUNE_COND_INT_LESS = '<',
+       RUNE_COND_INT_GREATER = '>',
+       RUNE_COND_LEXO_BEFORE = '{',
+       RUNE_COND_LEXO_AFTER = '}',
+       RUNE_COND_COMMENT = '#',
+};
+
+/* An alternative is a utf-8 fieldname, a condition, and a value */
+struct rune_altern {
+       enum rune_condition condition;
+       /* Strings. */
+       const char *fieldname, *value;
+};
+
+/**
+ * rune_new - Create an unrestricted rune from this secret.
+ * @ctx: tal context, or NULL.  Freeing @ctx will free the returned rune.
+ * @secret: secret bytes.
+ * @secret_len: number of @secret bytes (must be 55 bytes or less)
+ * @version: if non-NULL, sets a version for this rune.
+ *
+ * This allocates a new, unrestricted rune (sometimes called a master rune).
+ *
+ * Setting a version allows for different interpretations of a rune if
+ * things change in future, at cost of some space when it's used.
+ *
+ * Example:
+ *  u8 secret[16];
+ *  struct rune *master;
+ *
+ *  // A secret determined with a fair die roll!
+ *  memset(secret, 5, sizeof(secret));
+ *  master = rune_new(NULL, secret, sizeof(secret), NULL);
+ *  assert(master);
+ */
+struct rune *rune_new(const tal_t *ctx, const u8 *secret, size_t secret_len,
+                     const char *version);
+
+/**
+ * rune_derive_start - Copy master rune, add a unique id.
+ * @ctx: context to allocate rune off
+ * @master: master rune.
+ * @unique_id: unique id; can be NULL, but that's not recommended.
+ *
+ * It's usually recommended to assign each rune a unique_id, so that
+ * specific runes can be blacklisted later (otherwise you need to disable
+ * all runes).  This enlarges the rune string by '=<unique_id>' however.
+ *
+ * The rune version will be the same as the master: if that's non-zero,
+ * you *must* set unique_id.
+ *
+ * @unique_id cannot contain '-'.
+ *
+ * Example:
+ *  struct rune *rune;
+ *  // In reality, some global incrementing variable.
+ *  const char *id = "1";
+ *  rune = rune_derive_start(NULL, master, id);
+ *  assert(rune);
+ */
+struct rune *rune_derive_start(const tal_t *ctx,
+                              const struct rune *master,
+                              const char *unique_id);
+
+/**
+ * rune_dup - Copy a rune.
+ * @ctx: tal context, or NULL.
+ * @altern: the altern to copy.
+ *
+ * If @altern is take(), then simply returns it, otherwise copies.
+ */
+struct rune *rune_dup(const tal_t *ctx, const struct rune *rune TAKES);
+
+/**
+ * rune_altern_new - Create a new alternative.
+ * @ctx: tal context, or NULL.  Freeing @ctx will free the returned altern.
+ * @fieldname: the UTF-8 field for the altern.  You can only have
+ *             alphanumerics, '.', '-' and '_' here.
+ * @condition: the condition, defined above.
+ * @value: the value for comparison; use "" if you don't care.  Any UTF-8 value
+ *         is allowed.
+ *
+ * An altern is the basis of rune restrictions (technically, a restriction
+ * is one or more alterns, but it's often just one).
+ *
+ * Example:
+ *  struct rune_altern *a1, *a2;
+ *  a1 = rune_altern_new(NULL, "val", RUNE_COND_EQUAL, "7");
+ *  a2 = rune_altern_new(NULL, "val2", '>', "-1");
+ *  assert(a1 && a2);
+ */
+struct rune_altern *rune_altern_new(const tal_t *ctx,
+                                   const char *fieldname TAKES,
+                                   enum rune_condition condition,
+                                   const char *value TAKES);
+
+/**
+ * rune_altern_dup - copy an alternative.
+ * @ctx: tal context, or NULL.
+ * @altern: the altern to copy.
+ *
+ * If @altern is take(), then simply returns it, otherwise copies.
+ */
+struct rune_altern *rune_altern_dup(const tal_t *ctx,
+                                   const struct rune_altern *altern TAKES);
+
+/**
+ * rune_restr_new - Create a new (empty) restriction.
+ * @ctx: tal context, or NULL.  Freeing @ctx will free the returned restriction.
+ *
+ * Example:
+ *  struct rune_restr *restr = rune_restr_new(NULL);
+ *  assert(restr);
+ */
+struct rune_restr *rune_restr_new(const tal_t *ctx);
+
+/**
+ * rune_restr_dup - copy a restr.
+ * @ctx: tal context, or NULL.
+ * @restr: the restr to copy.
+ *
+ * If @resttr is take(), then simply returns it, otherwise copies.
+ */
+struct rune_restr *rune_restr_dup(const tal_t *ctx,
+                                 const struct rune_restr *restr TAKES);
+
+/**
+ * rune_restr_add_altern - add an altern to this restriction
+ * @restr: the restriction to add to
+ * @alt: the altern.
+ *
+ * If the alt is take(alt) then the alt will be owned by the restriction,
+ * otherwise it's copied.
+ *
+ * Example:
+ *  rune_restr_add_altern(restr, take(a1));
+ *  rune_restr_add_altern(restr, take(a2));
+ */
+void rune_restr_add_altern(struct rune_restr *restr,
+                          const struct rune_altern *alt TAKES);
+
+/**
+ * rune_add_restr - add a restriction to this rune
+ * @rune: the rune to add to.
+ * @restr: the (non-empty) restriction.
+ *
+ * If the alt is take(alt) then the alt will be owned by the restr,
+ * otherwise it's copied (and all its children are copied!).
+ *
+ * This fails (and returns false) if restr tries to set unique_id/version
+ * and is not the first restriction, or has more than one alternative,
+ * or uses a non '=' condition.
+ *
+ * Example:
+ *  rune_add_restr(rune, take(restr));
+ */
+bool rune_add_restr(struct rune *rune,
+                   const struct rune_restr *restr TAKES);
+
+/**
+ * rune_altern_eq - are two rune_altern equivalent?
+ * @alt1: the first
+ * @alt2: the second
+ */
+bool rune_altern_eq(const struct rune_altern *alt1,
+                   const struct rune_altern *alt2);
+
+/**
+ * rune_restr_eq - are two rune_restr equivalent?
+ * @rest1: the first
+ * @rest2: the second
+ */
+bool rune_restr_eq(const struct rune_restr *rest1,
+                  const struct rune_restr *rest2);
+
+/**
+ * rune_eq - are two runes equivalent?
+ * @rest1: the first
+ * @rest2: the second
+ */
+bool rune_eq(const struct rune *rune1, const struct rune *rune2);
+
+/**
+ * rune_alt_single - helper to implement check().
+ * @ctx: context to allocate any error return from.
+ * @alt: alternative to test.
+ * @fieldval_str: field value as a string.
+ * @fieldval_int: field value as an integer.
+ *
+ * If the field value is missing, set neither fieldval_str nor fieldval_int,
+ * otherwise you must set exactly one.
+ */
+const char *rune_alt_single(const tal_t *ctx,
+                           const struct rune_altern *alt,
+                           const char *fieldval_str,
+                           const s64 *fieldval_int);
+
+/**
+ * rune_is_derived - is a rune derived from this other rune?
+ * @source: the base rune (usually the master rune)
+ * @rune: the rune to check.
+ *
+ * This is the first part of "is this rune valid?": does the cryptography
+ * check out, such that they validly made the rune from this source rune?
+ *
+ * It also checks that the versions match: if you want to allow more than
+ * one version, see rune_is_derived_anyversion.
+ */
+const char *rune_is_derived(const struct rune *source, const struct rune *rune);
+
+/**
+ * rune_is_derived_anyversion - is a rune derived from this other rune?
+ * @source: the base rune (usually the master rune)
+ * @rune: the rune to check.
+ *
+ * This does not check source->version against rune->version: if you issue
+ * different rune versions you will need to check that yourself.
+ */
+const char *rune_is_derived_anyversion(const struct rune *source,
+                                      const struct rune *rune);
+
+/**
+ * rune_meets_criteria - do we meet the criteria specified by the rune?
+ * @ctx: the tal context to allocate the returned error off.
+ * @rune: the rune to check.
+ * @check: the callback to check values
+ * @arg: data to hand to @check
+ *
+ * This is the second part of "is this rune valid?".
+ */
+const char *rune_meets_criteria_(const tal_t *ctx,
+                                const struct rune *rune,
+                                const char *(*check)(const tal_t *ctx,
+                                                     const struct rune *rune,
+                                                     const struct rune_altern *alt,
+                                                     void *arg),
+                                void *arg);
+
+/* Typesafe wrapper */
+#define rune_meets_criteria(ctx, rune, check, arg)                     \
+       rune_meets_criteria_(typesafe_cb_preargs(const char *, void *,  \
+                                                (ctx), (rune),         \
+                                                (check), (arg),        \
+                                                const tal_t *,         \
+                                                const struct rune *,   \
+                                                const struct rune_altern *), \
+                            (arg))
+
+/**
+ * rune_test - is a rune authorized?
+ * @ctx: the tal context to allocate @errstr off.
+ * @master: the master rune created from secret.
+ * @rune: the rune to check.
+ * @errstr: if non-NULL, descriptive string of failure.
+ * @get: the callback to get values
+ * @arg: data to hand to callback
+ *
+ * Simple call for rune_is_derived() and rune_meets_criteria().  If
+ * it's not OK, returns non-NULL.
+ */
+const char *rune_test_(const tal_t *ctx,
+                      const struct rune *master,
+                      const struct rune *rune,
+                      const char *(*check)(const tal_t *ctx,
+                                           const struct rune *rune,
+                                           const struct rune_altern *alt,
+                                           void *arg),
+                      void *arg);
+
+/* Typesafe wrapper */
+#define rune_test(ctx_, master_, rune_, check_, arg_)                  \
+       rune_test_((ctx_), (master_), (rune_),                          \
+                  typesafe_cb_preargs(const char *, void *,            \
+                                      (check_), (arg_),                \
+                                      const tal_t *,                   \
+                                      const struct rune *,             \
+                                      const struct rune_altern *),     \
+                  (arg_))
+
+
+/**
+ * rune_from_base64 - convert base64 string to rune.
+ * @ctx: context to allocate rune off.
+ * @str: base64 string.
+ *
+ * Returns NULL if it's malformed.
+ */
+struct rune *rune_from_base64(const tal_t *ctx, const char *str);
+
+/**
+ * rune_to_base64 - convert run to base64 string.
+ * @ctx: context to allocate rune off.
+ * @rune: the rune.
+ *
+ * Only returns NULL if you've allowed tal allocations to return NULL.
+ */
+char *rune_to_base64(const tal_t *ctx, const struct rune *rune);
+
+/**
+ * This is a much more convenient working form.
+ */
+struct rune *rune_from_string(const tal_t *ctx, const char *str);
+char *rune_to_string(const tal_t *ctx, const struct rune *rune);
+
+/**
+ * rune_restr_from_string - convenience routine to parse a single restriction.
+ * @ctx: context to allocate rune off.
+ * @str: the string of form "<field><cond><val>[|<field><cond><val>]*"
+ *
+ * This is really an internal routine, exposed for simple examples.
+ */
+struct rune_restr *rune_restr_from_string(const tal_t *ctx,
+                                         const char *str);
+
+#endif /* CCAN_RUNE_RUNE_H */
diff --git a/ccan/rune/test/run.c b/ccan/rune/test/run.c
new file mode 100644 (file)
index 0000000..6cf33a3
--- /dev/null
@@ -0,0 +1,127 @@
+#include <ccan/rune/rune.c>
+#include <ccan/rune/coding.c>
+#include <ccan/tal/grab_file/grab_file.h>
+#include <ccan/tal/str/str.h>
+#include <ccan/tap/tap.h>
+
+static const char *check(const tal_t *ctx,
+                        const struct rune *rune,
+                        const struct rune_altern *alt,
+                        char **parts)
+{
+       const char *val = NULL;
+
+       for (size_t i = 1; parts[i]; i++) {
+               if (strstarts(parts[i], alt->fieldname)
+                   && parts[i][strlen(alt->fieldname)] == '=')
+                       val = parts[i] + strlen(alt->fieldname) + 1;
+       }
+
+       /* If it's an integer, hand it like that */
+       if (val) {
+               char *endp;
+               s64 v = strtol(val, &endp, 10);
+               if (*endp == '\0' && endp != val)
+                       return rune_alt_single(ctx, alt, NULL, &v);
+       }
+
+       return rune_alt_single(ctx, alt, val, NULL);
+}
+
+int main(void)
+{
+       char *vecs;
+       char **lines;
+       static const u8 secret_zero[16];
+       struct rune *mr;
+
+       /* Test vector rune uses all-zero secret */
+       mr = rune_new(NULL, secret_zero, sizeof(secret_zero), NULL); 
+
+       /* Python runes library generates test vectors */
+       vecs = grab_file(mr, "test/test_vectors.csv");
+       assert(vecs);
+       lines = tal_strsplit(mr, take(vecs), "\n", STR_NO_EMPTY);
+
+       plan_tests(343);
+
+       for (size_t i = 0; lines[i]; i++) {
+               struct rune *rune1, *rune2;
+               char **parts;
+
+               parts = tal_strsplit(lines, lines[i], ",", STR_EMPTY_OK);
+               if (streq(parts[0], "VALID")) {
+                       diag("test %s %s", parts[0], parts[1]);
+                       rune1 = rune_from_string(parts, parts[2]);
+                       ok1(rune1);
+                       rune2 = rune_from_base64(parts, parts[3]);
+                       ok1(rune2);
+                       ok1(rune_eq(rune1, rune2));
+                       ok1(streq(rune_to_string(parts, rune2), parts[2]));
+                       ok1(streq(rune_to_base64(parts, rune1), parts[3]));
+                       ok1(rune_is_derived_anyversion(mr, rune1) == NULL);
+                       ok1(rune_is_derived_anyversion(mr, rune2) == NULL);
+
+                       if (parts[4]) {
+                               if (parts[5])
+                                       ok1(streq(rune1->version, parts[5]));
+                               ok1(streq(rune1->unique_id, parts[4]));
+                       } else {
+                               ok1(!rune1->version);
+                               ok1(!rune1->unique_id);
+                       }
+                       mr->version = NULL;
+               } else if (streq(parts[0], "DERIVE")) {
+                       struct rune_restr *restr;
+                       diag("test %s %s", parts[0], parts[1]);
+                       rune1 = rune_from_base64(parts, parts[2]);
+                       ok1(rune1);
+                       rune2 = rune_from_base64(parts, parts[3]);
+                       ok1(rune2);
+                       ok1(rune_is_derived_anyversion(mr, rune1) == NULL);
+                       ok1(rune_is_derived_anyversion(mr, rune2) == NULL);
+                       ok1(rune_is_derived_anyversion(rune1, rune2) == NULL);
+
+                       restr = rune_restr_new(NULL);
+                       for (size_t i = 4; parts[i]; i+=3) {
+                               struct rune_altern *alt;
+                               alt = rune_altern_new(NULL,
+                                                     parts[i],
+                                                     parts[i+1][0],
+                                                     parts[i+2]);
+                               rune_restr_add_altern(restr, take(alt));
+                       }
+                       rune_add_restr(rune1, take(restr));
+                       ok1(rune_eq(rune1, rune2));
+               } else if (streq(parts[0], "MALFORMED")) {
+                       diag("test %s %s", parts[0], parts[1]);
+                       rune1 = rune_from_string(parts, parts[2]);
+                       ok1(!rune1);
+                       rune2 = rune_from_base64(parts, parts[3]);
+                       ok1(!rune2);
+               } else if (streq(parts[0], "BAD DERIVATION")) {
+                       diag("test %s %s", parts[0], parts[1]);
+                       rune1 = rune_from_string(parts, parts[2]);
+                       ok1(rune1);
+                       rune2 = rune_from_base64(parts, parts[3]);
+                       ok1(rune2);
+                       ok1(rune_eq(rune1, rune2));
+                       ok1(rune_is_derived(mr, rune1) != NULL);
+                       ok1(rune_is_derived(mr, rune2) != NULL);
+               } else {
+                       const char *err;
+                       diag("test %s", parts[0]);
+                       err = rune_test(parts, mr, rune1, check, parts);
+                       if (streq(parts[0], "PASS")) {
+                               ok1(!err);
+                       } else {
+                               assert(streq(parts[0], "FAIL"));
+                               ok1(err);
+                       }
+               }
+       }
+
+       tal_free(mr);
+       /* This exits depending on whether all tests passed */
+       return exit_status();
+}
diff --git a/ccan/rune/test/test_vectors.csv b/ccan/rune/test/test_vectors.csv
new file mode 100644 (file)
index 0000000..a841169
--- /dev/null
@@ -0,0 +1,151 @@
+VALID,empty rune (secret = [0]*16),374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb:,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s=
+PASS
+PASS,f1=1
+PASS,f1=var
+PASS,f1=\|\&\\
+VALID,unique id 1,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:=1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL09MQ==,1
+VALID,unique id 2 version 1,4520773407c9658646326fdffe685ffbc3c8639a080dae4310b371830a205cf1:=2-1,RSB3NAfJZYZGMm_f_mhf-8PIY5oIDa5DELNxgwogXPE9Mi0x,2,1
+VALID,f1 is missing,64a926b7185d7cf98e10a07dfc4e83d2a826896ebdb112ac964566fa2d50b464:f1!,ZKkmtxhdfPmOEKB9_E6D0qgmiW69sRKslkVm-i1QtGRmMSE=
+PASS
+PASS,f2=f1
+FAIL,f1=1
+FAIL,f1=var
+VALID,f1 equals v1,745c6e39cd41ee9f8388af8ad882bae4ee4e8f6b373f7682cc64d8574551fa5f:f1=v1,dFxuOc1B7p-DiK-K2IK65O5Oj2s3P3aCzGTYV0VR-l9mMT12MQ==
+PASS,f1=v1
+FAIL,f1=v
+FAIL,f1=v1a
+FAIL
+FAIL,f2=f1
+VALID,f1 not equal v1,c9236a6532bfa8e24bec9a66e96af3fb355f817770e79c5a81f6dd0b5ed20e47:f1/v1,ySNqZTK_qOJL7Jpm6Wrz-zVfgXdw55xagfbdC17SDkdmMS92MQ==
+PASS,f1=v2
+PASS,f1=v
+PASS,f1=v1a
+FAIL
+FAIL,f2=v1
+VALID,f1 ends with v1,71f2a1ec9631efc75b01db15fe1f025327ab467f8a83e6bfa7506da222adc5a2:f1$v1,cfKh7JYx78dbAdsV_h8CUyerRn-Kg-a_p1BtoiKtxaJmMSR2MQ==
+PASS,f1=v1
+PASS,f1=2v1
+FAIL,f1=v1a
+FAIL
+VALID,f1 starts with v1,5b13dffbbd9f7b191b0557595d10b22c0acec0c567f8efeba1d7d047927d7bce:f1^v1,WxPf-72fexkbBVdZXRCyLArOwMVn-O_rodfQR5J9e85mMV52MQ==
+PASS,f1=v1
+PASS,f1=v1a
+FAIL,f1=2v1
+FAIL
+VALID,f1 contains v1,ccbe593b72e0ab29446e46796ccd0c775ecd7a327fcc9ddc00fd3910cdacca00:f1~v1,zL5ZO3LgqylEbkZ5bM0Md17NejJ_zJ3cAP05EM2sygBmMX52MQ==
+PASS,f1=v1
+PASS,f1=v1a
+PASS,f1=2v1
+PASS,f1=2v12
+FAIL,f1=1v2
+FAIL
+VALID,f1 less than v1,caff52cedb9241dc00aea7cefc2b89b0a7445b1a4e34c48a5a2b91d2fe76d31f:f1<v1,yv9SztuSQdwArqfO_CuJsKdEWxpONMSKWiuR0v520x9mMTx2MQ==
+FAIL,f1=1
+FAIL,f1=2
+FAIL,f1=v1
+FAIL
+VALID,f1 less than 1,f9776db54fb54c8dd6af20a65a0f210a752a0ee4d1b0a0e7fd9d7ef65af76f84:f1<1,-XdttU-1TI3WryCmWg8hCnUqDuTRsKDn_Z1-9lr3b4RmMTwx
+PASS,f1=0
+PASS,f1=-10000
+FAIL,f1=1
+FAIL,f1=10000
+FAIL,f1=v1
+FAIL
+VALID,f1 greater than v1,2135748f1956d9dfa3c5b09ab6af9d6bb06a41c5bcf93d3f8105cb278af5ac56:f1>v1,ITV0jxlW2d-jxbCatq-da7BqQcW8-T0_gQXLJ4r1rFZmMT52MQ==
+FAIL,f1=1
+FAIL,f1=2
+FAIL,f1=v1
+FAIL
+VALID,f1 greater than 1,84e9991dd941bac97cc681eefec5dd7ac3668a4490ca6b0f19f0e79d2bb9c746:f1>1,hOmZHdlBusl8xoHu_sXdesNmikSQymsPGfDnnSu5x0ZmMT4x
+PASS,f1=2
+PASS,f1=10000
+FAIL,f1=1
+FAIL,f1=-10000
+FAIL,f1=0
+FAIL,f1=v1
+FAIL
+VALID,f1 sorts before 11,b9653ad0dcad7e5ed183f98cdd7e616acd07a98cc66a107a67626290bf000236:f1{11,uWU60Nytfl7Rg_mM3X5has0HqYzGahB6Z2JikL8AAjZmMXsxMQ==
+PASS,f1=0
+PASS,f1=1
+PASS,f1=       
+PASS,f1=/
+FAIL,f1=11
+FAIL,f1=111
+FAIL,f1=v1
+FAIL,f1=:
+FAIL
+VALID,f1 sorts after 11,8c1f6c7c39badc5dea850192a0a4c6e9dd96bf33d410adc5a08fc375b22a1a52:f1}11,jB9sfDm63F3qhQGSoKTG6d2WvzPUEK3FoI_DdbIqGlJmMX0xMQ==
+PASS,f1=111
+PASS,f1=v1
+PASS,f1=:
+FAIL,f1=0
+FAIL,f1=1
+FAIL,f1=       
+FAIL,f1=/
+FAIL,f1=11
+FAIL
+VALID,f1 comment 11,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1#11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSMxMQ==
+PASS,f1=111
+PASS,f1=v1
+PASS,f1=:
+PASS,f1=0
+PASS,f1=1
+PASS,f1=       
+PASS,f1=/
+PASS,f1=11
+PASS
+VALID,f1=1 or f2=3,85c3643dc102f0a0d6f20eeb8c294092151688fae41ef7c8ec7272ab23918376:f1=1|f2=3,hcNkPcEC8KDW8g7rjClAkhUWiPrkHvfI7HJyqyORg3ZmMT0xfGYyPTM=
+PASS,f1=1
+PASS,f1=1,f2=2
+PASS,f2=3
+PASS,f1=var,f2=3
+PASS,f1=1,f2=3
+FAIL
+FAIL,f1=2
+FAIL,f1=f1
+FAIL,f2=1
+FAIL,f2=f1
+DERIVE,unique_id 1 derivation,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s=,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL09MQ==,,=,1
+DERIVE,unique_id 2 version 1 derivation,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s=,RSB3NAfJZYZGMm_f_mhf-8PIY5oIDa5DELNxgwogXPE9Mi0x,,=,2-1
+DERIVE,f1=1 or f2=3,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s=,hcNkPcEC8KDW8g7rjClAkhUWiPrkHvfI7HJyqyORg3ZmMT0xfGYyPTM=,f1,=,1,f2,=,3
+DERIVE,AND f3 contains &|\,hcNkPcEC8KDW8g7rjClAkhUWiPrkHvfI7HJyqyORg3ZmMT0xfGYyPTM=,S253BW1Lragb1CpCSLXYGt9AdrE4iFMlXmnO0alV5vlmMT0xfGYyPTMmZjN-XCZcfFxc,f3,~,&|\
+PASS,f1=1,f3=&|\
+PASS,f2=3,f3=&|\x
+FAIL
+FAIL,f1=1
+FAIL,f2=3
+FAIL,f1=1,f2=3
+FAIL,f1=2,f3=&|\
+FAIL,f2=2,f3=&|\
+FAIL,f3=&|\
+MALFORMED,unique id must use = not !,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:!1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0hMQ==
+MALFORMED,unique id must use = not /,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:/1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0vMQ==
+MALFORMED,unique id must use = not ^,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:^1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL1eMQ==
+MALFORMED,unique id must use = not $,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:$1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0kMQ==
+MALFORMED,unique id must use = not ~,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:~1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL1-MQ==
+MALFORMED,unique id must use = not <,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:<1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL08MQ==
+MALFORMED,unique id must use = not >,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:>1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0-MQ==
+MALFORMED,unique id must use = not },6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:}1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL19MQ==
+MALFORMED,unique id must use = not {,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:{1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL17MQ==
+MALFORMED,unique id cannot be overridden,7a63a2966d38e6fed89256d4a6e983a6813bf084d4fc6c20b9cdaef24b23fa7e:=1-2&=3,emOilm045v7YklbUpumDpoE78ITU_Gwguc2u8ksj-n49MS0yJj0z
+MALFORMED,version cannot be overridden,db823224f960976b3ee142ce8899fc7ea461b42617e7d16167b1886c5988c628:=1-2&=1-3,24IyJPlgl2s-4ULOiJn8fqRhtCYX59FhZ7GIbFmIxig9MS0yJj0xLTM=
+MALFORMED,Bad condition ",76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1"11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSIxMQ==
+MALFORMED,Bad condition &,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1&11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSYxMQ==
+MALFORMED,Bad condition ',76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1'11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMScxMQ==
+MALFORMED,Bad condition (,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1(11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSgxMQ==
+MALFORMED,Bad condition ),76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1)11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSkxMQ==
+MALFORMED,Bad condition *,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1*11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSoxMQ==
+MALFORMED,Bad condition +,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1+11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSsxMQ==
+MALFORMED,Bad condition -,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1-11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMS0xMQ==
+MALFORMED,Bad condition .,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1.11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMS4xMQ==
+MALFORMED,Bad condition :,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1:11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMToxMQ==
+MALFORMED,Bad condition ;,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1;11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMTsxMQ==
+MALFORMED,Bad condition ?,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1?11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMT8xMQ==
+MALFORMED,Bad condition [,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1[11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMVsxMQ==
+MALFORMED,Bad condition \,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1\11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMVwxMQ==
+MALFORMED,Bad condition ],76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1]11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMV0xMQ==
+MALFORMED,Bad condition _,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1_11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMV8xMQ==
+MALFORMED,Bad condition `,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1`11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMWAxMQ==
+MALFORMED,Bad condition |,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1|11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMXwxMQ==
+BAD DERIVATION,Incremented sha,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0e:f1#11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw5mMSMxMQ==
+BAD DERIVATION,Unchanged sha,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1#11&a=1,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSMxMSZhPTE=