Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 3 additions & 37 deletions common/bolt12_proof.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,46 +166,13 @@ struct tlv_payer_proof *make_unsigned_proof_(const tal_t *ctx,
return pptlv;
}

struct tlv0_adding_leaf_iter {
const struct tlv_field *fields;
struct tlv_field tlv0;
int n;
};

static const struct tlv_field *next_field_prepend_tlv0(bool *is_omitted,
struct tlv0_adding_leaf_iter *iter)
{
*is_omitted = false;
if (iter->n == -1) {
iter->n = 0;
return &iter->tlv0;
}
if (iter->n >= tal_count(iter->fields))
return NULL;
return &iter->fields[iter->n++];
}

/* BOLT-payer_proof #12:
* - MUST set `proof_signature` as detailed in [Signature Calculation](#signature-calculation) using the `invreq_payer_id` using the merkle-root as the `msg` and a `first_tlv` value of 0x0000 (i.e. type 0, length 0).
* - MUST set `proof_signature` as detailed in [Signature Calculation](#signature-calculation) using the `invreq_payer_id` using the merkle-root as the `msg`.
*/
void bolt12_payer_proof_merkle(const struct tlv_payer_proof *proof,
struct sha256 *merkle)
{
struct tlv0_adding_leaf_iter iter;

/* We use a modified iterator to insert tlv0. */
iter.fields = proof->fields;
iter.n = -1;
iter.tlv0.meta = NULL;
iter.tlv0.numtype = 0;
iter.tlv0.length = 0;
iter.tlv0.value = NULL;

merkle_tlv_full(merkle,
next_field_prepend_tlv0,
bolt12_calc_nonce,
NULL,
&iter);
merkle_tlv(proof->fields, merkle);
}

struct bip340sig *payer_proof_signature_(const tal_t *ctx,
Expand Down Expand Up @@ -479,8 +446,7 @@ const char *check_payer_proof(const tal_t *ctx,
*...
* - `proof_signature` is not a valid signature using
* `invreq_payer_id` as described in [Signature
* Calculation](#signature-calculation), using `msg` merkle-root and
* a `first_tlv` value of 0x0000 (i.e. type 0, length 0).
* Calculation](#signature-calculation), using `msg` merkle-root.
*/
bolt12_payer_proof_merkle(pptlv, &merkle);
sighash_from_merkle("payer_proof", "proof_signature", &merkle, &shash);
Expand Down
112 changes: 112 additions & 0 deletions common/test/run-bolt12_proof.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,118 @@ int main(int argc, char *argv[])
assert(check_payer_proof(tmpctx, proof) == NULL);
}

/* Make sure that full proof notices if any field is altered! */
for (size_t i = 0;; i++) {
u8 *tlvstream;
const u8 *p;
size_t len;
struct tlv_payer_proof *tlvpp;
struct sha256 merkle, shash;

proof = make_unsigned_proof(tmpctx, inv, &preimage, "test",
exclude_this, int2ptr(0));
proof->proof_signature = payer_proof_signature(proof, proof, sign,
&kp);
tlv_update_fields(proof, tlv_payer_proof, &proof->fields);

if (i == tal_count(proof->fields))
break;

switch (proof->fields[i].numtype) {
/* Simple ones: we can increment the first byte to
* change the value (and have it still decode) */
case TLV_PAYER_PROOF_OFFER_CHAINS:
case TLV_PAYER_PROOF_OFFER_METADATA:
case TLV_PAYER_PROOF_OFFER_CURRENCY:
case TLV_PAYER_PROOF_OFFER_AMOUNT:
case TLV_PAYER_PROOF_OFFER_DESCRIPTION:
case TLV_PAYER_PROOF_OFFER_ABSOLUTE_EXPIRY:
case TLV_PAYER_PROOF_OFFER_ISSUER:
case TLV_PAYER_PROOF_OFFER_QUANTITY_MAX:
case TLV_PAYER_PROOF_INVREQ_CHAIN:
case TLV_PAYER_PROOF_INVREQ_AMOUNT:
case TLV_PAYER_PROOF_INVREQ_QUANTITY:
case TLV_PAYER_PROOF_INVREQ_PAYER_NOTE:
case TLV_PAYER_PROOF_INVREQ_BIP_353_NAME:
case TLV_PAYER_PROOF_INVOICE_CREATED_AT:
case TLV_PAYER_PROOF_INVOICE_RELATIVE_EXPIRY:
case TLV_PAYER_PROOF_INVOICE_AMOUNT:
case TLV_PAYER_PROOF_PROOF_NOTE:
case TLV_PAYER_PROOF_INVOICE_PAYMENT_HASH:
case TLV_PAYER_PROOF_PROOF_PREIMAGE:
proof->fields[i].value[0]++;
break;

/* Put in random signature */
case TLV_PAYER_PROOF_SIGNATURE:
case TLV_PAYER_PROOF_PROOF_SIGNATURE: {
struct bip340sig sig;
struct sha256 msg;
memset(&msg, 0, sizeof(msg));
sign("", "", &msg, &sig, &kp);
assert(proof->fields[i].length == sizeof(sig));
memcpy(proof->fields[i].value, &sig, sizeof(sig));
break;
}
/* Flip bit 1 here */
case TLV_PAYER_PROOF_INVOICE_FEATURES:
case TLV_PAYER_PROOF_OFFER_FEATURES:
case TLV_PAYER_PROOF_INVREQ_FEATURES:
proof->fields[i].value[0] ^= 2;
break;

/* Truncate these */
case TLV_PAYER_PROOF_INVREQ_PATHS:
case TLV_PAYER_PROOF_INVOICE_PATHS:
case TLV_PAYER_PROOF_PROOF_OMITTED_TLVS:
case TLV_PAYER_PROOF_PROOF_MISSING_HASHES:
case TLV_PAYER_PROOF_PROOF_LEAF_HASHES:
case TLV_PAYER_PROOF_INVOICE_FALLBACKS:
case TLV_PAYER_PROOF_INVOICE_BLINDEDPAY:
case TLV_PAYER_PROOF_OFFER_PATHS:
proof->fields[i].length = 0;
break;

/* Replace with random pubkey */
case TLV_PAYER_PROOF_INVOICE_NODE_ID:
case TLV_PAYER_PROOF_INVREQ_PAYER_ID:
case TLV_PAYER_PROOF_OFFER_ISSUER_ID: {
struct secret secret;
struct pubkey pk;

memset(&secret, 7, sizeof(secret));
pubkey_from_secret(&secret, &pk);
assert(proof->fields[i].length == PUBKEY_CMPR_LEN);
pubkey_to_der(proof->fields[i].value, &pk);
break;
}
}

tlvstream = tal_arr(tmpctx, u8, 0);
towire_tlvstream_raw(&tlvstream, proof->fields);

/* Should demarshal OK */
len = tal_bytelen(tlvstream);
p = tlvstream;
tlvpp = fromwire_tlv_payer_proof(tmpctx, &p, &len);
assert(tlvpp);
assert(p != NULL);
assert(len == 0);

/* Proof sig should be invalid if we changed anything!*/
bolt12_payer_proof_merkle(tlvpp, &merkle);
sighash_from_merkle("payer_proof", "proof_signature",
&merkle, &shash);

/* Except the invoice signature (that's checked separately). */
if (!check_schnorr_sig(&shash, &tlvpp->invreq_payer_id->pubkey,
tlvpp->proof_signature)) {
assert(proof->fields[i].numtype != TLV_PAYER_PROOF_SIGNATURE);
} else {
assert(proof->fields[i].numtype == TLV_PAYER_PROOF_SIGNATURE);
}
}

common_shutdown();
return 0;
}
22 changes: 12 additions & 10 deletions common/test/run-bolt12_proof_vectors.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ static void generate_valid_vector(const char *name,
bool (*include_field)(const struct tlv_field *, void *),
bool explicit_empty_omitted)
{
struct sha256 mroot, shash;
struct sha256 mroot, pproot, shash;
struct tlv_payer_proof *proof;
secp256k1_keypair kp = keypair_for_letter(payer_letter);
u8 *invoice_wire;
Expand All @@ -179,6 +179,10 @@ static void generate_valid_vector(const char *name,
}
proof->proof_signature = payer_proof_signature(proof, proof, sign_payer, &kp);
assert(proof->proof_signature);
/* Refresh fields to include proof_signature */
tlv_update_fields(proof, tlv_payer_proof, &proof->fields);

bolt12_payer_proof_merkle(proof, &pproot);

printf("{\n");
printf("\"name\":\"%s\",\n", name);
Expand All @@ -189,7 +193,8 @@ static void generate_valid_vector(const char *name,
tal_hexstr(tmpctx, invoice_wire, tal_bytelen(invoice_wire)));
printf("\"preimage\":\"%s\",\n",
tal_hexstr(tmpctx, preimage->r, sizeof(preimage->r)));
printf("\"note\":\"%s\",\n", note);
if (note)
printf("\"note\":\"%s\",\n", note);
printf("\"invoice_fields\":[\n");
print_fields_json(inv->fields, tal_count(inv->fields), include_field, NULL);
printf("]\n");
Expand All @@ -200,7 +205,7 @@ static void generate_valid_vector(const char *name,
printf("\"invoice_sighash\":\"%s\",\n", fmt_sha256(tmpctx, &shash));
printf("\"invoice_signature\":\"%s\",\n",
tal_hexstr(tmpctx, inv->signature->u8, sizeof(inv->signature->u8)));
printf("\"proof_merkle_root\":\"%s\",\n", fmt_sha256(tmpctx, &mroot));
printf("\"proof_merkle_root\":\"%s\",\n", fmt_sha256(tmpctx, &pproot));
printf("\"proof_leaf_hashes\":[\n");
print_hashes_json(proof->proof_leaf_hashes, tal_count(proof->proof_leaf_hashes));
printf("],\n");
Expand All @@ -212,9 +217,6 @@ static void generate_valid_vector(const char *name,
printf("]\n");
printf("},\n");

/* Refresh fields to include proof_signature */
tlv_update_fields(proof, tlv_payer_proof, &proof->fields);

printf("\"result\":{\n");
printf("\"payer_sig\":\"%s\",\n",
fmt_bip340sig(tmpctx, proof->proof_signature));
Expand Down Expand Up @@ -540,7 +542,7 @@ int main(int argc, char *argv[])
assert(inv);

printf("\"valid_vectors\":[\n");
generate_valid_vector("full_disclosure", inv, &preimage, 'B', "", include_all, false);
generate_valid_vector("full_disclosure", inv, &preimage, 'B', NULL, include_all, false);
printf(",\n");
/* For the rest, remove features and experimental field: keep it vanilla */
inv->invoice_features = tal_free(inv->invoice_features);
Expand All @@ -553,7 +555,7 @@ int main(int argc, char *argv[])
inv = invoice_decode(tmpctx, invstr, strlen(invstr), NULL, NULL, &fail);
assert(inv);

generate_valid_vector("minimal_disclosure", inv, &preimage, 'B', "",
generate_valid_vector("minimal_disclosure", inv, &preimage, 'B', NULL,
include_minimal, false);
printf(",\n");
generate_valid_vector("with_note", inv, &preimage, 'B', "test note",
Expand All @@ -564,13 +566,13 @@ int main(int argc, char *argv[])
* is entirely omitted, so its subtree hash appears in proof_missing_hashes AFTER
* the hash for the adjacent type82 leaf (which shares the same 4-leaf subtree
* and is resolved first during DFS). */
generate_valid_vector("left_subtree_omitted", inv, &preimage, 'B', "",
generate_valid_vector("left_subtree_omitted", inv, &preimage, 'B', NULL,
include_amount, false);
printf(",\n");
/* This vector demonstrates that proof_omitted_tlvs present with length 0 is
* accepted identically to the field being absent. The spec says writers MAY
* omit the field when empty, so readers must accept both forms. */
generate_valid_vector("empty_proof_omitted_tlvs_explicit", inv, &preimage, 'B', "",
generate_valid_vector("empty_proof_omitted_tlvs_explicit", inv, &preimage, 'B', NULL,
include_all, true);
printf("\n],\n");

Expand Down
Loading