From bf38ca052ea481f150e69344ebacb77289a01bc2 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 30 May 2015 20:11:10 +0930 Subject: [PATCH] open-commit-sig: create signature for commit tx. Signed-off-by: Rusty Russell --- Makefile | 8 +++- anchor.c | 41 +++++++++++++++++- anchor.h | 8 ++++ bitcoin_script.c | 71 ++++++++++++++++++++++++++++++ bitcoin_script.h | 10 +++++ lightning.pb-c.c | 6 +-- lightning.pb-c.h | 2 +- lightning.proto | 2 +- open-anchor-sig.c | 40 ++++++----------- open-commit-sig.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++ pkt.c | 15 ++++++- pkt.h | 8 ++++ signature.c | 25 +++++++++++ signature.h | 5 +++ 14 files changed, 313 insertions(+), 36 deletions(-) create mode 100644 open-commit-sig.c diff --git a/Makefile b/Makefile index d44b3b4ca..ab6030cfa 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Needs to have oneof support: Ubuntu vivid's is too old :( PROTOCC:=protoc-c -PROGRAMS := open-channel open-anchor-sig leak-anchor-sigs +PROGRAMS := open-channel open-anchor-sig leak-anchor-sigs open-commit-sig HELPER_OBJS := base58.o lightning.pb-c.o shadouble.o pkt.o bitcoin_script.o permute_tx.o signature.o bitcoin_tx.o bitcoin_address.o anchor.o @@ -12,6 +12,7 @@ CCAN_OBJS := ccan-crypto-sha256.o ccan-crypto-shachain.o ccan-err.o ccan-tal.o c OPEN_CHANNEL_OBJS := open-channel.o OPEN_ANCHOR_SIG_OBJS := open-anchor-sig.o LEAK_ANCHOR_SIGS_OBJS := leak-anchor-sigs.o +OPEN_COMMIT_SIG_OBJS := open-commit-sig.o HEADERS := $(wildcard *.h) @@ -33,12 +34,15 @@ $(OPEN_ANCHOR_SIG_OBJS): $(HEADERS) leak-anchor-sigs: $(LEAK_ANCHOR_SIGS_OBJS) $(HELPER_OBJS) $(CCAN_OBJS) $(LEAK_ANCHOR_SIGS_OBJS): $(HEADERS) +open-commit-sig: $(OPEN_COMMIT_SIG_OBJS) $(HELPER_OBJS) $(CCAN_OBJS) +$(OPEN_COMMIT_SIG_OBJS): $(HEADERS) + distclean: clean $(RM) lightning.pb-c.c lightning.pb-c.h clean: $(RM) $(PROGRAMS) - $(RM) $(OPEN_CHANNEL_OBJS) $(OPEN_ANCHOR_SIG_OBJS) $(LEAK_ANCHOR_SIGS_OBJS) $(HELPER_OBJS) $(CCAN_OBJS) + $(RM) $(OPEN_CHANNEL_OBJS) $(OPEN_ANCHOR_SIG_OBJS) $(LEAK_ANCHOR_SIGS_OBJS) $(OPEN_COMMIT_SIG_OBJS) $(HELPER_OBJS) $(CCAN_OBJS) ccan-tal.o: $(CCANDIR)/ccan/tal/tal.c $(CC) $(CFLAGS) -c -o $@ $< diff --git a/anchor.c b/anchor.c index a3449356c..0085b9803 100644 --- a/anchor.c +++ b/anchor.c @@ -4,6 +4,7 @@ #include "pkt.h" #include "permute_tx.h" #include "bitcoin_script.h" +#include struct bitcoin_tx *anchor_tx_create(const tal_t *ctx, const OpenChannel *o1, @@ -16,7 +17,7 @@ struct bitcoin_tx *anchor_tx_create(const tal_t *ctx, size_t *inmap, *outmap; if (add_overflows_size_t(o1->anchor->n_inputs, o2->anchor->n_inputs)) - return tal_free(tx); + return NULL; n_out = 1 + !!o1->anchor->change + !!o2->anchor->change; tx = bitcoin_tx(ctx, o1->anchor->n_inputs+o2->anchor->n_inputs, n_out); @@ -88,4 +89,42 @@ struct bitcoin_tx *anchor_tx_create(const tal_t *ctx, return tx; } +void anchor_txid(struct bitcoin_tx *anchor, + const char *leakfile1, const char *leakfile2, + const size_t *inmap, + struct sha256_double *txid) +{ + Pkt *p1, *p2; + LeakAnchorSigsAndPretendWeDidnt *leak1, *leak2; + size_t i; + struct sha256_ctx shactx; + p1 = pkt_from_file(leakfile1, PKT__PKT_OMG_FAIL); + p2 = pkt_from_file(leakfile2, PKT__PKT_OMG_FAIL); + leak1 = p1->omg_fail; + leak2 = p2->omg_fail; + + if (leak1->sigs->n_script + leak2->sigs->n_script != anchor->input_count) + errx(1, "Expected %llu total inputs, not %zu + %zu", + (long long)anchor->input_count, + leak1->sigs->n_script, leak2->sigs->n_script); + + for (i = 0; i < leak1->sigs->n_script; i++) { + anchor->input[i].script = leak1->sigs->script[i].data; + anchor->input[i].script_length = leak1->sigs->script[i].len; + } + + for (i = 0; i < leak2->sigs->n_script; i++) { + anchor->input[leak1->sigs->n_script + i].script + = leak2->sigs->script[i].data; + anchor->input[leak1->sigs->n_script + i].script_length + = leak2->sigs->script[i].len; + } + + sha256_init(&shactx); + sha256_tx(&shactx, anchor); + sha256_double_done(&shactx, txid); + + pkt__free_unpacked(p1, NULL); + pkt__free_unpacked(p2, NULL); +} diff --git a/anchor.h b/anchor.h index 1ab02b6af..d26922eb2 100644 --- a/anchor.h +++ b/anchor.h @@ -3,6 +3,8 @@ #include #include "lightning.pb-c.h" +struct sha256_double; + /* Create an anchor transaction based on both sides' requests. * The scriptSigs are left empty. * @@ -16,4 +18,10 @@ struct bitcoin_tx *anchor_tx_create(const tal_t *ctx, const OpenChannel *o1, const OpenChannel *o2, size_t **inmap, size_t **outmap); + +/* We wouldn't need the leak files if we had normalized txids! */ +void anchor_txid(struct bitcoin_tx *anchor, + const char *leakfile1, const char *leakfile2, + const size_t *inmap, + struct sha256_double *txid); #endif /* LIGHTNING_ANCHOR_H */ diff --git a/bitcoin_script.c b/bitcoin_script.c index a9765de50..49cb4f482 100644 --- a/bitcoin_script.c +++ b/bitcoin_script.c @@ -1,5 +1,6 @@ #include "bitcoin_script.h" #include "bitcoin_address.h" +#include "pkt.h" #include #include #include @@ -11,12 +12,19 @@ #define OP_PUSHDATA2 0x4D #define OP_PUSHDATA4 0x4E #define OP_NOP 0x61 +#define OP_IF 0x63 +#define OP_ELSE 0x67 +#define OP_ENDIF 0x68 +#define OP_DEPTH 0x74 #define OP_DUP 0x76 #define OP_EQUAL 0x87 #define OP_EQUALVERIFY 0x88 +#define OP_SIZE 0x82 +#define OP_1SUB 0x8C #define OP_CHECKSIG 0xAC #define OP_CHECKMULTISIG 0xAE #define OP_HASH160 0xA9 +#define OP_CHECKSEQUENCEVERIFY 0xB2 static void add(u8 **scriptp, const void *mem, size_t len) { @@ -160,3 +168,66 @@ bool is_pay_to_pubkey_hash(const ProtobufCBinaryData *script) return false; return true; } + +/* One of: + * mysig and theirsig, OR + * mysig and relative locktime passed, OR + * theirsig and hash preimage. */ +u8 *bitcoin_redeem_revocable(const tal_t *ctx, + const BitcoinPubkey *mykey, + u32 locktime, + const BitcoinPubkey *theirkey, + const Sha256Hash *revocation_hash) +{ + u8 *script = tal_arr(ctx, u8, 0); + struct sha256 rhash; + u8 rhash_ripemd[RIPEMD160_DIGEST_LENGTH]; + le32 locktime_le = cpu_to_le32(locktime); + + proto_to_sha256(revocation_hash, &rhash); + + /* If there are two args: */ + add_op(&script, OP_DEPTH); + add_op(&script, OP_1SUB); + add_op(&script, OP_IF); + + /* If the top arg is a hashpreimage. */ + add_op(&script, OP_SIZE); + add_op(&script, OP_LITERAL(32)); + add_op(&script, OP_EQUAL); + add_op(&script, OP_IF); + + /* Must hash to revocation_hash, and be signed by them. */ + RIPEMD160(rhash.u.u8, sizeof(rhash.u), rhash_ripemd); + add_op(&script, OP_HASH160); + add_push_bytes(&script, rhash_ripemd, sizeof(rhash_ripemd)); + add_op(&script, OP_EQUALVERIFY); + add_push_bytes(&script, theirkey->key.data, theirkey->key.len); + add_op(&script, OP_CHECKSIG); + + /* Otherwise, it should be both our sigs. */ + add_op(&script, OP_ELSE); + + add_op(&script, OP_LITERAL(2)); + /* This obscures whose key is whose. Probably unnecessary? */ + if (key_less(mykey, theirkey)) { + add_push_bytes(&script, mykey->key.data, mykey->key.len); + add_push_bytes(&script, theirkey->key.data, theirkey->key.len); + } else { + add_push_bytes(&script, theirkey->key.data, theirkey->key.len); + add_push_bytes(&script, mykey->key.data, mykey->key.len); + } + add_op(&script, OP_LITERAL(2)); + add_op(&script, OP_CHECKMULTISIG); + add_op(&script, OP_ENDIF); + + /* Not two args? Must be us using timeout. */ + add_op(&script, OP_ELSE); + add_push_bytes(&script, &locktime_le, sizeof(locktime_le)); + add_op(&script, OP_CHECKSEQUENCEVERIFY); + add_push_bytes(&script, mykey->key.data, mykey->key.len); + add_op(&script, OP_CHECKSIG); + add_op(&script, OP_ENDIF); + + return script; +} diff --git a/bitcoin_script.h b/bitcoin_script.h index 79950ecee..62f06852d 100644 --- a/bitcoin_script.h +++ b/bitcoin_script.h @@ -15,6 +15,16 @@ u8 *bitcoin_redeem_2of2(const tal_t *ctx, /* tal_count() gives the length of the script. */ u8 *bitcoin_redeem_single(const tal_t *ctx, const u8 *key, size_t keylen); +/* One of: + * mysig and theirsig, OR + * mysig and relative locktime passed, OR + * theirsig and hash preimage. */ +u8 *bitcoin_redeem_revocable(const tal_t *ctx, + const BitcoinPubkey *mykey, + u32 locktime, + const BitcoinPubkey *theirkey, + const Sha256Hash *revocation_hash); + /* Create an output script using p2sh for this redeem script. */ u8 *scriptpubkey_p2sh(const tal_t *ctx, const u8 *redeemscript); diff --git a/lightning.pb-c.c b/lightning.pb-c.c index 49a14b5ac..661953124 100644 --- a/lightning.pb-c.c +++ b/lightning.pb-c.c @@ -1579,12 +1579,12 @@ const ProtobufCMessageDescriptor open_anchor_sig__descriptor = static const ProtobufCFieldDescriptor leak_anchor_sigs_and_pretend_we_didnt__field_descriptors[1] = { { - "anchor_scriptsigs", + "sigs", 1, PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_MESSAGE, 0, /* quantifier_offset */ - offsetof(LeakAnchorSigsAndPretendWeDidnt, anchor_scriptsigs), + offsetof(LeakAnchorSigsAndPretendWeDidnt, sigs), &open_anchor_sig__descriptor, NULL, 0, /* flags */ @@ -1592,7 +1592,7 @@ static const ProtobufCFieldDescriptor leak_anchor_sigs_and_pretend_we_didnt__fie }, }; static const unsigned leak_anchor_sigs_and_pretend_we_didnt__field_indices_by_name[] = { - 0, /* field[0] = anchor_scriptsigs */ + 0, /* field[0] = sigs */ }; static const ProtobufCIntRange leak_anchor_sigs_and_pretend_we_didnt__number_ranges[1 + 1] = { diff --git a/lightning.pb-c.h b/lightning.pb-c.h index 376017545..554918482 100644 --- a/lightning.pb-c.h +++ b/lightning.pb-c.h @@ -248,7 +248,7 @@ struct _OpenAnchorSig struct _LeakAnchorSigsAndPretendWeDidnt { ProtobufCMessage base; - OpenAnchorSig *anchor_scriptsigs; + OpenAnchorSig *sigs; }; #define LEAK_ANCHOR_SIGS_AND_PRETEND_WE_DIDNT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&leak_anchor_sigs_and_pretend_we_didnt__descriptor) \ diff --git a/lightning.proto b/lightning.proto index c9a7851db..4381d8ff9 100644 --- a/lightning.proto +++ b/lightning.proto @@ -99,7 +99,7 @@ message open_anchor_sig { // malleability anyway). So for testing, we send the scriptsigs for the // anchor transaction's inputs immediately. message leak_anchor_sigs_and_pretend_we_didnt { - required open_anchor_sig anchor_scriptsigs = 1; + required open_anchor_sig sigs = 1; } // Indicates we've seen transaction reach min-depth. diff --git a/open-anchor-sig.c b/open-anchor-sig.c index b292d52c1..58665a2eb 100644 --- a/open-anchor-sig.c +++ b/open-anchor-sig.c @@ -18,33 +18,19 @@ #include /* All the input scripts are already set to 0. We just need to make this one. */ -static u8 *sign_tx_input(const tal_t *ctx, - struct bitcoin_tx *tx, - unsigned int i, - const BitcoinInput *input, - EC_KEY *privkey, - const struct bitcoin_compressed_pubkey *pubkey) +static u8 *tx_scriptsig(const tal_t *ctx, + struct bitcoin_tx *tx, + unsigned int i, + const BitcoinInput *input, + EC_KEY *privkey, + const struct bitcoin_compressed_pubkey *pubkey) { - struct sha256_double hash; - struct sha256_ctx shactx; - struct bitcoin_address addr; u8 *sig; + struct bitcoin_address addr; - /* Transaction gets signed as if the output subscript is the - * only input script. */ - tx->input[i].script_length = input->subscript.len; - tx->input[i].script = input->subscript.data; - - sha256_init(&shactx); - sha256_tx(&shactx, tx); - sha256_le32(&shactx, SIGHASH_ALL); - sha256_double_done(&shactx, &hash); - - /* Reset it for next time. */ - tx->input[i].script_length = 0; - tx->input[i].script = NULL; - - sig = sign_hash(ctx, privkey, &hash); + sig = sign_tx_input(ctx, tx, i, + input->subscript.data, input->subscript.len, + privkey); if (!sig) return NULL; @@ -102,9 +88,9 @@ int main(int argc, char *argv[]) if (!testnet) errx(1, "Private key '%s' not on testnet!", argv[3+i]); - sigs[i] = sign_tx_input(sigs, anchor, map[i], - o1->anchor->inputs[i], - privkey, &pubkey); + sigs[i] = tx_scriptsig(sigs, anchor, map[i], + o1->anchor->inputs[i], + privkey, &pubkey); } pkt = open_anchor_sig_pkt(ctx, sigs, o1->anchor->n_inputs); diff --git a/open-commit-sig.c b/open-commit-sig.c new file mode 100644 index 000000000..1ff842b69 --- /dev/null +++ b/open-commit-sig.c @@ -0,0 +1,108 @@ +/* My example: + * ./open-commit-sig A-open.pb B-open.pb cUBCjrdJu8tfvM7FT8So6aqs6G6bZS1Cax6Rc9rFzYL6nYG4XNEC A-leak-anchor-sigs.pb B-leak-anchor-sigs.pb > A-commit-sig.pb + * ./open-commit-sig B-open.pb A-open.pb cQXhbUnNRsFcdzTQwjbCrud5yVskHTEas7tZPUWoJYNk5htGQrpi B-leak-anchor-sigs.pb A-leak-anchor-sigs.pb > B-commit-sig.pb + */ +#include +#include +#include +#include +#include +#include +#include +#include "lightning.pb-c.h" +#include "anchor.h" +#include "base58.h" +#include "pkt.h" +#include "bitcoin_script.h" +#include "permute_tx.h" +#include "signature.h" +#include +#include + +int main(int argc, char *argv[]) +{ + const tal_t *ctx = tal_arr(NULL, char, 0); + OpenChannel *o1, *o2; + struct bitcoin_tx *anchor, *commit; + struct sha256_double txid; + struct pkt *pkt; + u8 *redeemscript, *sig; + size_t *inmap, *outmap; + EC_KEY *privkey; + bool testnet; + struct bitcoin_compressed_pubkey pubkey; + + err_set_progname(argv[0]); + + opt_register_noarg("--help|-h", opt_usage_and_exit, + " \n" + "Create the signature needed for the commit transaction", + "Print this message."); + + opt_parse(&argc, argv, opt_log_stderr_exit); + + if (argc != 6) + opt_usage_and_exit(NULL); + + o1 = pkt_from_file(argv[1], PKT__PKT_OPEN)->open; + o2 = pkt_from_file(argv[2], PKT__PKT_OPEN)->open; + + privkey = key_from_base58(argv[3], strlen(argv[3]), &testnet, &pubkey); + if (!privkey) + errx(1, "Invalid private key '%s'", argv[3]); + if (!testnet) + errx(1, "Private key '%s' not on testnet!", argv[3]); + + /* Create merged anchor transaction */ + anchor = anchor_tx_create(ctx, o1, o2, &inmap, &outmap); + if (!anchor) + errx(1, "Failed transaction merge"); + + /* Get the transaction ID of the anchor. */ + anchor_txid(anchor, argv[4], argv[5], inmap, &txid); + + /* Now create commitment tx: one input, two outputs. */ + commit = bitcoin_tx(ctx, 1, 2); + + /* Our input spends the anchor tx output. */ + commit->input[0].txid = txid; + commit->input[0].index = outmap[0]; + + /* First output is a P2SH to a complex redeem script */ + redeemscript = bitcoin_redeem_revocable(ctx, o1->anchor->pubkey, + o1->locktime_seconds, + o2->anchor->pubkey, + o1->revocation_hash); + commit->output[0].script = scriptpubkey_p2sh(ctx, redeemscript); + commit->output[0].script_length = tal_count(commit->output[0].script); + + if (o1->anchor->total < o1->commitment_fee) + errx(1, "Our contribution to channel %llu < fee %llu", + (long long)o1->anchor->total, + (long long)o1->commitment_fee); + commit->output[0].amount = o1->anchor->total - o1->commitment_fee; + + /* Second output is a simple payment to them. */ + commit->output[1].script = o2->script_to_me.data; + commit->output[1].script_length = o2->script_to_me.len; + + if (o2->anchor->total < o2->commitment_fee) + errx(1, "Their contribution to channel %llu < fee %llu", + (long long)o2->anchor->total, + (long long)o2->commitment_fee); + commit->output[1].amount = o2->anchor->total - o2->commitment_fee; + + permute_outputs(o1->seed, o2->seed, 1, commit->output, 2, NULL); + + sig = sign_tx_input(ctx, commit, 0, anchor->output[outmap[0]].script, + anchor->output[outmap[0]].script_length, privkey); + + pkt = open_commit_sig_pkt(ctx, sig, tal_count(sig)); + if (!write_all(STDOUT_FILENO, pkt, + sizeof(pkt->len) + le32_to_cpu(pkt->len))) + err(1, "Writing out packet"); + + tal_free(ctx); + return 0; +} + diff --git a/pkt.c b/pkt.c index 278cfb907..98f6df87c 100644 --- a/pkt.c +++ b/pkt.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "pkt.h" #include "bitcoin_tx.h" #include "bitcoin_address.h" @@ -137,6 +138,18 @@ struct pkt *leak_anchor_sigs_and_pretend_we_didnt_pkt(const tal_t *ctx, LeakAnchorSigsAndPretendWeDidnt omg_fail = LEAK_ANCHOR_SIGS_AND_PRETEND_WE_DIDNT__INIT; - omg_fail.anchor_scriptsigs = s; + omg_fail.sigs = s; return to_pkt(ctx, PKT__PKT_OMG_FAIL, &omg_fail); } + +struct pkt *open_commit_sig_pkt(const tal_t *ctx, const u8 *sig, size_t siglen) +{ + OpenCommitSig o = OPEN_COMMIT_SIG__INIT; + BitcoinSignature s = BITCOIN_SIGNATURE__INIT; + + o.sig = &s; + s.der_then_sigtype.len = siglen; + s.der_then_sigtype.data = cast_const(u8 *, sig); + + return to_pkt(ctx, PKT__PKT_OPEN_COMMIT_SIG, &o); +} diff --git a/pkt.h b/pkt.h index 833a71932..6645b4701 100644 --- a/pkt.h +++ b/pkt.h @@ -58,6 +58,14 @@ struct pkt *leak_anchor_sigs_and_pretend_we_didnt_pkt(const tal_t *ctx, OpenAnchorSig *s); +/** + * open_commit_sig_pkt - create an open_commit_sig message + * @ctx: tal context to allocate off. + * @sig: the der-encoded signature for the commit transaction input. + * @siglen: the length of @sig. + */ +struct pkt *open_commit_sig_pkt(const tal_t *ctx, const u8 *sig, size_t siglen); + /* Useful helper for allocating & populating a protobuf Sha256Hash */ Sha256Hash *sha256_to_proto(const tal_t *ctx, const struct sha256 *hash); void proto_to_sha256(const Sha256Hash *pb, struct sha256 *hash); diff --git a/signature.c b/signature.c index d9cd51941..9b585c9d3 100644 --- a/signature.c +++ b/signature.c @@ -1,8 +1,10 @@ #include "signature.h" #include "shadouble.h" +#include "bitcoin_tx.h" #include #include #include +#include u8 *sign_hash(const tal_t *ctx, EC_KEY *private_key, const struct sha256_double *h) @@ -43,3 +45,26 @@ u8 *sign_hash(const tal_t *ctx, EC_KEY *private_key, OPENSSL_free(der); return ret; } + +u8 *sign_tx_input(const tal_t *ctx, struct bitcoin_tx *tx, unsigned int in, + const u8 *subscript, size_t subscript_len, EC_KEY *privkey) +{ + struct sha256_double hash; + struct sha256_ctx shactx; + + /* Transaction gets signed as if the output subscript is the + * only input script. */ + tx->input[in].script_length = subscript_len; + tx->input[in].script = cast_const(u8 *, subscript); + + sha256_init(&shactx); + sha256_tx(&shactx, tx); + sha256_le32(&shactx, SIGHASH_ALL); + sha256_double_done(&shactx, &hash); + + /* Reset it for next time. */ + tx->input[in].script_length = 0; + tx->input[in].script = NULL; + + return sign_hash(ctx, privkey, &hash); +} diff --git a/signature.h b/signature.h index a819e741a..d08941e82 100644 --- a/signature.h +++ b/signature.h @@ -12,8 +12,13 @@ enum sighash_type { }; struct sha256_double; +struct bitcoin_tx; u8 *sign_hash(const tal_t *ctx, EC_KEY *private_key, const struct sha256_double *h); +/* All tx input scripts must be set to 0 len. */ +u8 *sign_tx_input(const tal_t *ctx, struct bitcoin_tx *tx, unsigned int in, + const u8 *subscript, size_t subscript_len, EC_KEY *privkey); + #endif /* LIGHTNING_SIGNATURE_H */