From 8275faaf2fa6989829c976a54da120def4cfeda8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 23 Jun 2022 14:07:37 +0930 Subject: [PATCH] rune: new module to implement runes. Signed-off-by: Rusty Russell --- ccan/rune/LICENSE | 1 + ccan/rune/_info | 131 +++++++++ ccan/rune/coding.c | 417 +++++++++++++++++++++++++++++ ccan/rune/internal.h | 8 + ccan/rune/rune.c | 454 ++++++++++++++++++++++++++++++++ ccan/rune/rune.h | 349 ++++++++++++++++++++++++ ccan/rune/test/run.c | 127 +++++++++ ccan/rune/test/test_vectors.csv | 151 +++++++++++ 8 files changed, 1638 insertions(+) create mode 120000 ccan/rune/LICENSE create mode 100644 ccan/rune/_info create mode 100644 ccan/rune/coding.c create mode 100644 ccan/rune/internal.h create mode 100644 ccan/rune/rune.c create mode 100644 ccan/rune/rune.h create mode 100644 ccan/rune/test/run.c create mode 100644 ccan/rune/test/test_vectors.csv diff --git a/ccan/rune/LICENSE b/ccan/rune/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/rune/LICENSE @@ -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 index 00000000..e3601560 --- /dev/null +++ b/ccan/rune/_info @@ -0,0 +1,131 @@ +#include "config.h" +#include +#include + +/** + * 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 + * 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 + * #include + * #include + * #include + * #include + * + * // 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 OR\n" + * "%s add OR\n" + * "%s test ", 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 index 00000000..201553d8 --- /dev/null +++ b/ccan/rune/coding.c @@ -0,0 +1,417 @@ +/* MIT (BSD) license - see LICENSE file for details */ +/* Routines to encode / decode a rune */ +#include +#include +#include +#include +#include +#include +#include + +/* 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 index 00000000..e4de06cc --- /dev/null +++ b/ccan/rune/internal.h @@ -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 index 00000000..36eaab92 --- /dev/null +++ b/ccan/rune/rune.c @@ -0,0 +1,454 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 index 00000000..a284b733 --- /dev/null +++ b/ccan/rune/rune.h @@ -0,0 +1,349 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#ifndef CCAN_RUNE_RUNE_H +#define CCAN_RUNE_RUNE_H +#include +#include +#include +#include + +/* 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 '=' 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 "[|]*" + * + * 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 index 00000000..6cf33a3d --- /dev/null +++ b/ccan/rune/test/run.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include + +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 index 00000000..a8411693 --- /dev/null +++ b/ccan/rune/test/test_vectors.csv @@ -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:f1v1,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= -- 2.39.2