// SPDX-License-Identifier: WTFPL // cc -Wall -O2 -I/usr/local/include -L/usr/local/lib -ljansson -lcrypto -lsecp256k1 event-sign2.c -o event-sign2 #include #include #include #include #include #include #include static void *malloc_debug(size_t size) { void *ptr = malloc(size); printf("%p malloc(%zu)\n", ptr, size); return ptr; } static void free_debug(void *ptr) { printf("%p free\n", ptr); } static void encode_hex(char *out, int outsize, unsigned char *in, int insize) { static char c[] = "0123456789abcdef"; int i, j; for (i = j = 0; i < outsize - 1 && j < insize; j++) { out[i++] = c[(in[j] >> 4) & 0x0f]; if (i < outsize - 1) out[i++] = c[in[j] & 0x0f]; } out[i] = '\0'; } static int decode_hex(unsigned char *buf, int buflen, const char *str) { int i, p, n; char *tmp; n = buflen * 2; tmp = alloca(n); memset(tmp, 0, n); for (i = 0, p = strlen(str) - 1; i < n; i++, p--) { if (str[p] >= '0' && str[p] <= '9') tmp[i] = str[p] - '0'; else if (str[p] >= 'a' && str[p] <= 'f') tmp[i] = str[p] - 'a' + 10; else if (str[p] >= 'A' && str[p] <= 'F') tmp[i] = str[p] - 'A' + 10; else return -1; } for (i = 0, p = n - 1; i < buflen; i++, p -= 2) buf[i] = (tmp[p] << 4) | tmp[p - 1]; return 0; } int sign_nostr_event(json_t *jobject, unsigned char *seckey) { json_t *jarray, *jpubkey, *jcreated_at, *jkind, *jtags, *jcontent; char *dump, tmp[256]; unsigned char id[32], sig[64], random_seed[32], pubkey[32]; secp256k1_context *ctx; secp256k1_keypair keypair; secp256k1_xonly_pubkey xpubkey; int v = -1; /* initialize secp256k1 engine and create pubkey from seckey */ if ((ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN)) == NULL) { fprintf(stderr, "sign_nostr_event: secp256k1_context_create\n"); goto fin0; } arc4random_buf(random_seed, sizeof(random_seed)); if (!secp256k1_context_randomize(ctx, random_seed)) { fprintf(stderr, "sign_nostr_event: secp256k1_context_randomize\n"); goto fin1; } if (!secp256k1_keypair_create(ctx, &keypair, seckey)) { fprintf(stderr, "sign_nostr_event: secp256k1_keypair_create\n"); goto fin1; } if (!secp256k1_keypair_xonly_pub(ctx, &xpubkey, NULL, &keypair)) { fprintf(stderr, "sign_nostr_event: secp256k1_keypair_xonly_pub\n"); goto fin1; } if (!secp256k1_xonly_pubkey_serialize(ctx, pubkey, &xpubkey)) { fprintf(stderr, "sign_nostr_event: secp256k1_xonly_pubkey_serialize\n"); goto fin1; } /* set pubkey */ encode_hex(tmp, sizeof(tmp), pubkey, sizeof(pubkey)); if (json_object_set(jobject, "pubkey", json_string(tmp))) { fprintf(stderr, "sign_nostr_event: json_object_set\n"); goto fin1; } /* get object */ jpubkey = json_object_get(jobject, "pubkey"); jcreated_at = json_object_get(jobject, "created_at"); jkind = json_object_get(jobject, "kind"); jtags = json_object_get(jobject, "tags"); jcontent = json_object_get(jobject, "content"); /* create array */ if ((jarray = json_array()) == NULL) { fprintf(stderr, "sign_nostr_event: json_array\n"); goto fin1; } if (json_array_append(jarray, json_integer(0)) || (jpubkey != NULL && json_array_append(jarray, jpubkey)) || (jcreated_at != NULL && json_array_append(jarray, jcreated_at)) || (jkind != NULL && json_array_append(jarray, jkind)) || (jtags != NULL && json_array_append(jarray, jtags)) || (jcontent != NULL && json_array_append(jarray, jcontent))) { fprintf(stderr, "sign_nostr_event: json_array_append\n"); goto fin2; } /* calculate SHA256 message digest (id) */ if ((dump = json_dumps(jarray, JSON_COMPACT | JSON_ENCODE_ANY)) == NULL) { fprintf(stderr, "sign_nostr_event: json_dumps\n"); goto fin2; } SHA256((unsigned char *)dump, strlen(dump), id); encode_hex(tmp, sizeof(tmp), id, sizeof(id)); if (json_object_set(jobject, "id", json_string(tmp))) { fprintf(stderr, "sign_nostr_event: json_object_set\n"); goto fin3; } /* set signature */ arc4random_buf(random_seed, sizeof(random_seed)); if (!secp256k1_schnorrsig_sign32(ctx, sig, id, &keypair, random_seed)) { fprintf(stderr, "sign_nostr_event: secp256k1_schnorrsig_sign\n"); goto fin3; } encode_hex(tmp, sizeof(tmp), sig, sizeof(sig)); if (json_object_set(jobject, "sig", json_string(tmp))) { fprintf(stderr, "sign_nostr_event: json_object_set\n"); goto fin3; } v = 0; fin3: free_debug(dump); fin2: json_decref(jarray); fin1: secp256k1_context_destroy(ctx); fin0: return v; } int verify_nostr_event(json_t *object) { json_t *jid, *jpubkey, *jsig; unsigned char id[32], pubkey[32], sig[64]; secp256k1_context *ctx; secp256k1_xonly_pubkey xpubkey; int v = -1; /* get object */ if ((jid = json_object_get(object, "id")) == NULL || (jpubkey = json_object_get(object, "pubkey")) == NULL || (jsig = json_object_get(object, "sig")) == NULL) { fprintf(stderr, "verify_nostr_event: json_object_get\n"); goto fin0; } /* decode */ if (decode_hex(id, sizeof(id), json_string_value(jid)) || decode_hex(pubkey, sizeof(pubkey), json_string_value(jpubkey)) || decode_hex(sig, sizeof(sig), json_string_value(jsig))) { fprintf(stderr, "verify_nostr_event: decode_hex\n"); goto fin0; } /* verify (0:valid, 1:invalid) */ if ((ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY)) == NULL) { fprintf(stderr, "verify_nostr_event: secp256k1_context_create\n"); goto fin0; } if (!secp256k1_xonly_pubkey_parse(ctx, &xpubkey, pubkey)) { fprintf(stderr, "verify_nostr_event: secp256k1_xonly_pubkey_parse\n"); goto fin1; } v = secp256k1_schnorrsig_verify(ctx, sig, id, sizeof(id), &xpubkey) ? 0 : 1; fin1: secp256k1_context_destroy(ctx); fin0: return v; } int main(int argc, char *argv[]) { json_t *root; json_error_t error; unsigned char seckey[32]; char *buf, tmp[256]; if (argc < 2) { printf("usage: %s [filename]\n", argv[0]); goto fin0; } if (argc >= 4) json_set_alloc_funcs(malloc_debug, free_debug); if ((root = json_load_file(argv[1], 0, &error)) == NULL) { fprintf(stderr, "file open error\n"); goto fin0; } if (argc >= 3) { arc4random_buf(seckey, sizeof(seckey)); encode_hex(tmp, sizeof(tmp), seckey, sizeof(seckey)); fprintf(stderr, "seckey: %s\n", tmp); sign_nostr_event(root, seckey); } buf = json_dumps(root, JSON_ENCODE_ANY); printf("%s\n", buf); free_debug(buf); fprintf(stderr, "event is %s\n", verify_nostr_event(root) ? "invalid" : "valid"); //fin1: json_decref(root); fin0: return 0; }