--- /dev/null
+../../licenses/BSD-MIT
\ No newline at end of file
--- /dev/null
+#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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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 */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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 */
--- /dev/null
+#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();
+}
--- /dev/null
+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=