mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
draft boltzapi-v2 + taproot reverse swaps
This commit is contained in:
parent
3b6ff7a5ca
commit
c3dd1de86a
44 changed files with 2307 additions and 191 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -38,3 +38,4 @@ test_*
|
|||
!test_*.cpp
|
||||
!test_*.c
|
||||
/commit_hash.h
|
||||
*.swp
|
||||
|
|
|
|||
33
Bitcoin/pubkey_to_scriptPubKey.cpp
Normal file
33
Bitcoin/pubkey_to_scriptPubKey.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include"Bitcoin/pubkey_to_scriptPubKey.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
|
||||
namespace Bitcoin {
|
||||
#if 0
|
||||
std::vector<uint8_t>
|
||||
pk_to_scriptpk(Secp256k1::PubKey const& key) {
|
||||
auto rv = std::vector<std::uint8_t>(21);
|
||||
rv[0] = 0x00; /* OP_0, denoting segwit v0 */
|
||||
rv[1] = 0x14; /* varint for P2WPKH */
|
||||
key.to_buffer(&rv[2]);
|
||||
return rv;
|
||||
}
|
||||
#endif
|
||||
std::vector<uint8_t>
|
||||
pk_to_scriptpk(Secp256k1::PubKey const& key) {
|
||||
auto rv = std::vector<std::uint8_t>(34);
|
||||
rv[0] = 0x00; /* OP_0, denoting segwit v0 */
|
||||
rv[1] = 0x20; /* varint for P2WSH pubkey */
|
||||
key.to_buffer(&rv[2]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
std::vector<uint8_t>
|
||||
pk_to_scriptpk(Secp256k1::XonlyPubKey const& key) {
|
||||
auto rv = std::vector<std::uint8_t>(34);
|
||||
rv[0] = 0x51; /* OP_1, denoting segwit v1 */
|
||||
rv[1] = 0x20; /* varint for taproot pubkey */
|
||||
key.to_buffer(&rv[2]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
}
|
||||
17
Bitcoin/pubkey_to_scriptPubKey.hpp
Normal file
17
Bitcoin/pubkey_to_scriptPubKey.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef BITCOIN_PUBKEY_TO_SCRIPTPUBKEY_HPP
|
||||
#define BITCOIN_PUBKEY_TO_SCRIPTPUBKEY_HPP
|
||||
|
||||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
namespace Secp256k1 { class PubKey; }
|
||||
namespace Secp256k1 { class XonlyPubKey; }
|
||||
|
||||
namespace Bitcoin {
|
||||
|
||||
std::vector<uint8_t> pk_to_scriptpk(Secp256k1::PubKey const&);
|
||||
std::vector<uint8_t> pk_to_scriptpk(Secp256k1::XonlyPubKey const&);
|
||||
|
||||
}
|
||||
|
||||
#endif /* !defined(BITCOIN_PUBKEY_TO_SCRIPTPUBKEY_HPP) */
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include"Sha256/Hash.hpp"
|
||||
#include"Sha256/HasherStream.hpp"
|
||||
#include"Sha256/fun.hpp"
|
||||
#include"Secp256k1/tagged_hashes.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -16,11 +17,15 @@ void feed_hash(std::ostream& hasher, Sha256::Hash const& hash) {
|
|||
}
|
||||
|
||||
using ::Bitcoin::SighashFlags;
|
||||
using ::Bitcoin::SIGHASH_DEFAULT;
|
||||
using ::Bitcoin::SIGHASH_ALL;
|
||||
using ::Bitcoin::SIGHASH_NONE;
|
||||
using ::Bitcoin::SIGHASH_SINGLE;
|
||||
using ::Bitcoin::SIGHASH_ANYONECANPAY;
|
||||
using ::Bitcoin::InvalidSighash;
|
||||
using ::Bitcoin::p2trSpendType;
|
||||
using ::Bitcoin::KEYPATH;
|
||||
using ::Bitcoin::SCRIPTPATH;
|
||||
|
||||
void
|
||||
sighash_core( Bitcoin::Tx const& tx
|
||||
|
|
@ -109,6 +114,151 @@ sighash_core( Bitcoin::Tx const& tx
|
|||
;
|
||||
}
|
||||
|
||||
void
|
||||
p2tr_sighash_core( Bitcoin::Tx const& tx
|
||||
, SighashFlags flags
|
||||
, std::uint32_t nIn
|
||||
, std::vector<Ln::Amount> in_amounts
|
||||
, std::vector<std::vector<std::uint8_t>> const& scriptPubKeys
|
||||
, p2trSpendType spendtype
|
||||
, std::ostream& hasher
|
||||
) {
|
||||
/* spend type cannot exceed 7 bits to conform to bip341. */
|
||||
if (spendtype & 0x80)
|
||||
throw InvalidSighash("p2tr SpendType flag exceeds 0x7F");
|
||||
|
||||
auto loflags = flags & 0x1F;
|
||||
auto hiflags = flags & 0xE0;
|
||||
|
||||
switch (loflags) {
|
||||
case SIGHASH_DEFAULT:
|
||||
case SIGHASH_ALL:
|
||||
case SIGHASH_NONE:
|
||||
case SIGHASH_SINGLE:
|
||||
break;
|
||||
default:
|
||||
throw InvalidSighash("Invalid SIGHASH flag");
|
||||
}
|
||||
switch (hiflags) {
|
||||
case 0:
|
||||
case SIGHASH_ANYONECANPAY:
|
||||
break;
|
||||
default:
|
||||
throw InvalidSighash("Invalid SIGHASH flag");
|
||||
}
|
||||
|
||||
if (nIn >= tx.inputs.size())
|
||||
throw InvalidSighash("nIn out of range");
|
||||
|
||||
auto hashPrevouts = Sha256::Hash();
|
||||
if (!(hiflags & SIGHASH_ANYONECANPAY)) {
|
||||
Sha256::HasherStream hasher;
|
||||
for (auto const& i : tx.inputs)
|
||||
hasher << i.prevTxid
|
||||
<< Bitcoin::le(i.prevOut)
|
||||
;
|
||||
hashPrevouts = std::move(hasher).finalize();
|
||||
}
|
||||
|
||||
auto hashAmounts = Sha256::Hash();
|
||||
if (!(hiflags & SIGHASH_ANYONECANPAY)) {
|
||||
Sha256::HasherStream hasher;
|
||||
for (auto const& a : in_amounts)
|
||||
hasher << Bitcoin::le(a.to_sat());
|
||||
hashAmounts = std::move(hasher).finalize();
|
||||
}
|
||||
|
||||
auto hashScriptPubkeys = Sha256::Hash();
|
||||
if (!(hiflags & SIGHASH_ANYONECANPAY)) {
|
||||
Sha256::HasherStream hasher;
|
||||
for (auto const& spk : scriptPubKeys)
|
||||
for (auto b : spk)
|
||||
hasher.put(b);
|
||||
hashScriptPubkeys = std::move(hasher).finalize();
|
||||
}
|
||||
|
||||
auto hashSequences = Sha256::Hash();
|
||||
if (!(hiflags & SIGHASH_ANYONECANPAY)) {
|
||||
Sha256::HasherStream hasher;
|
||||
for (auto const& i : tx.inputs)
|
||||
hasher << Bitcoin::le(i.nSequence);
|
||||
hashSequences = std::move(hasher).finalize();
|
||||
}
|
||||
|
||||
auto hashOutputs = Sha256::Hash();
|
||||
if ( (loflags != SIGHASH_SINGLE)
|
||||
&& (loflags != SIGHASH_NONE)
|
||||
) {
|
||||
Sha256::HasherStream hasher;
|
||||
for (auto const& o : tx.outputs)
|
||||
hasher << o;
|
||||
hashOutputs = std::move(hasher).finalize();
|
||||
}
|
||||
|
||||
/* Hashing sequence. */
|
||||
/* 1. tx data */
|
||||
hasher << std::uint8_t(0x00) /* sighash epoch */
|
||||
<< std::uint8_t(flags) /* taproot spend type */
|
||||
<< Bitcoin::le(tx.nVersion)
|
||||
<< Bitcoin::le(tx.nLockTime);
|
||||
|
||||
if (!(hiflags & SIGHASH_ANYONECANPAY)) {
|
||||
feed_hash(hasher, hashPrevouts);
|
||||
feed_hash(hasher, hashAmounts);
|
||||
feed_hash(hasher, hashScriptPubkeys);
|
||||
feed_hash(hasher, hashSequences);
|
||||
}
|
||||
|
||||
if ( (loflags != SIGHASH_SINGLE)
|
||||
&& (loflags != SIGHASH_NONE)
|
||||
) {
|
||||
feed_hash(hasher, hashOutputs);
|
||||
}
|
||||
|
||||
/* 2. input data */
|
||||
/* spend type (bip341 ext_flag + optional annex flag).
|
||||
* NOTE: as of the present Bitcoin Core (v.28.0), there
|
||||
* is no support for interpreting annex data, and so
|
||||
* we cannot either. */
|
||||
hasher << std::uint8_t(spendtype);
|
||||
|
||||
if (hiflags & SIGHASH_ANYONECANPAY) {
|
||||
auto const& input = tx.inputs[nIn];
|
||||
/* outpoint as COutPoint */
|
||||
hasher << input.prevTxid
|
||||
<< Bitcoin::le(input.prevOut);
|
||||
/* little-endian input amount */
|
||||
hasher << Bitcoin::le(in_amounts[nIn].to_sat());
|
||||
/* varint(scriptPubKey) + scriptPubKey */
|
||||
auto const& spk = scriptPubKeys[nIn];
|
||||
for (auto b : spk)
|
||||
hasher.put(b);
|
||||
/* little-endian nSequence */
|
||||
hasher << Bitcoin::le(input.nSequence);
|
||||
} else {
|
||||
/* little-endian input number */
|
||||
hasher << Bitcoin::le(nIn);
|
||||
}
|
||||
|
||||
/* NOTE: a 32 byte sha-256 hash of formatted annex data
|
||||
* would be put into the sighash stream at this stage, if
|
||||
* 'annex present' was indicated in the ext_flag byte.
|
||||
* As noted above, Bitcoin Core does not support annex
|
||||
* data use in taproot transactions as of v.28.0 */
|
||||
|
||||
/* 3. output data */
|
||||
/* SIGHASH_SINGLE corresponding output. */
|
||||
if ((loflags == SIGHASH_SINGLE) && nIn < tx.outputs.size()) {
|
||||
{
|
||||
Sha256::HasherStream hasher;
|
||||
hasher << tx.outputs[nIn];
|
||||
hashOutputs = std::move(hasher).finalize();
|
||||
}
|
||||
|
||||
feed_hash(hasher, hashOutputs);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Bitcoin {
|
||||
|
|
@ -125,4 +275,18 @@ sighash( Bitcoin::Tx const& tx
|
|||
return Sha256::fun(std::move(hasher).finalize());
|
||||
}
|
||||
|
||||
Sha256::Hash
|
||||
p2tr_sighash( Bitcoin::Tx const& tx
|
||||
, SighashFlags flags
|
||||
, std::uint32_t nIn
|
||||
, std::vector<Ln::Amount> in_amounts
|
||||
, std::vector<std::vector<std::uint8_t>> const& scriptPubKeys
|
||||
, p2trSpendType spendtype
|
||||
) {
|
||||
auto hasher = Sha256::HasherStream(Tag::SIGHASH);
|
||||
p2tr_sighash_core(tx, flags, nIn, std::move(in_amounts), scriptPubKeys, spendtype, hasher);
|
||||
/* bip341 specifies single sha256 over sighash input data. */
|
||||
return std::move(hasher).finalize();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,18 @@ namespace Sha256 { class Hash; }
|
|||
namespace Bitcoin {
|
||||
|
||||
enum SighashFlags
|
||||
{ SIGHASH_ALL = 1
|
||||
{ SIGHASH_DEFAULT = 0 /* only for taproot/segwit v1 inputs */
|
||||
, SIGHASH_ALL = 1
|
||||
, SIGHASH_NONE = 2
|
||||
, SIGHASH_SINGLE = 3
|
||||
, SIGHASH_ANYONECANPAY = 0x80
|
||||
};
|
||||
|
||||
enum p2trSpendType
|
||||
{ KEYPATH = 0
|
||||
, SCRIPTPATH = 1
|
||||
};
|
||||
|
||||
struct InvalidSighash : public Util::BacktraceException<std::invalid_argument> {
|
||||
InvalidSighash() =delete;
|
||||
InvalidSighash(std::string const& msg)
|
||||
|
|
@ -58,6 +64,64 @@ sighash( Bitcoin::Tx const& tx
|
|||
, std::vector<std::uint8_t> const& scriptCode
|
||||
);
|
||||
|
||||
/** Bitcoin::p2tr_sighash
|
||||
*
|
||||
* @brief computes a bip341 spec sighash
|
||||
*
|
||||
* @desc taproot/schnorr spends require an
|
||||
* altogether different sighash type, specified
|
||||
* in bip 341. `spend_type` values higher than
|
||||
* 127 throw `Bitcoin::InvalidSighash`.
|
||||
*
|
||||
* bip341 signature digest:
|
||||
*
|
||||
* - all sha256 invocations are single only
|
||||
* - the final sha256 hash over all the data
|
||||
* must be a tagged hash as described in
|
||||
* bip340 (tag: "TapSighash")
|
||||
* - unconditionally present:
|
||||
* hashtype
|
||||
* nVersion
|
||||
* nLocktime
|
||||
* spend_type
|
||||
* - [number] indicates byte quantity
|
||||
*
|
||||
* [1] hashtype
|
||||
* [4] nVersion
|
||||
* [4] nLocktime
|
||||
* if (hashtype & 0x80 != SIGHASH_ANYONECANPAY)
|
||||
* [32] sha256(prevouts)
|
||||
* [32] sha256(amounts)
|
||||
* [32] sha256(scriptpubkeys)
|
||||
* [32] sha256(nSequences)
|
||||
*
|
||||
* if (hashtype & 0x03 != SIGHASH_SINGLE || hashtype & 0x03 != SIGHASH_ANYONECANPAY)
|
||||
* [32] sha256(outputs)
|
||||
*
|
||||
* [1] spendtype (bip341 ext_flag * 2 + annex flag)
|
||||
*
|
||||
* if (hashtype & 0x80 == SIGHASH_ANYONECANPAY)
|
||||
* [36] outpoint (32 byte txid + 4 byte output index)
|
||||
* [8] amount
|
||||
* [35] scriptpubkey
|
||||
* [4] nSequence
|
||||
* else
|
||||
* [4] inputindex
|
||||
*
|
||||
* if (spendtype & 0x01)
|
||||
* [32] sha256(compactsize(annex) + 0x50 + annex)
|
||||
*
|
||||
* if (hashtype & 0x03 == SIGHASH_SINGLE)
|
||||
* [32] sha256(nominatedoutput)
|
||||
*/
|
||||
Sha256::Hash
|
||||
p2tr_sighash( Bitcoin::Tx const& tx
|
||||
, SighashFlags flags
|
||||
, std::uint32_t nIn
|
||||
, std::vector<Ln::Amount> in_amounts
|
||||
, std::vector<std::vector<std::uint8_t>> const& scriptPubKeys
|
||||
, p2trSpendType spendtype
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* !defined(BITCOIN_SIGHASH_HPP) */
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
#include"Boltz/ConnectionIF.hpp"
|
||||
#include"Boltz/Detail/ClaimTxHandler.hpp"
|
||||
#include"Boltz/Detail/compute_preimage.hpp"
|
||||
#include"Boltz/Detail/find_lockup_outnum.hpp"
|
||||
#include"Boltz/Detail/initial_claim_tx.hpp"
|
||||
#include"Boltz/Detail/swaptree.hpp"
|
||||
#include"Boltz/EnvIF.hpp"
|
||||
#include"Bitcoin/pubkey_to_scriptPubKey.hpp"
|
||||
#include"Ev/Io.hpp"
|
||||
#include"Jsmn/Object.hpp"
|
||||
#include"Json/Out.hpp"
|
||||
#include"Secp256k1/SignerIF.hpp"
|
||||
#include"Secp256k1/Signature.hpp"
|
||||
#include"Secp256k1/TapscriptTree.hpp"
|
||||
#include"Sqlite3.hpp"
|
||||
#include"Util/Str.hpp"
|
||||
#include"Util/make_unique.hpp"
|
||||
#include<string.h>
|
||||
#include<sstream>
|
||||
|
||||
namespace {
|
||||
|
|
@ -16,6 +25,7 @@ struct End { };
|
|||
|
||||
}
|
||||
|
||||
using namespace Secp256k1;
|
||||
|
||||
namespace Boltz { namespace Detail {
|
||||
|
||||
|
|
@ -90,14 +100,18 @@ Ev::Io<void> ClaimTxHandler::core_run() {
|
|||
});
|
||||
}
|
||||
|
||||
auto claimScript = std::vector<std::uint8_t>();
|
||||
auto refundScript = std::vector<std::uint8_t>();
|
||||
/* Perform the actual fetch of the data. */
|
||||
auto fetch = tx.query(R"QRY(
|
||||
SELECT tweak -- 0
|
||||
, preimage -- 1
|
||||
, destinationAddress -- 2
|
||||
, redeemScript -- 3
|
||||
, timeoutBlockheight -- 4
|
||||
, onchainAmount -- 5
|
||||
, claimScript -- 3
|
||||
, refundScript -- 4
|
||||
, timeoutBlockheight -- 5
|
||||
, onchainAmount -- 6
|
||||
, refundPubKey -- 7
|
||||
FROM "BoltzServiceFactory_rsub"
|
||||
WHERE apiAccess = :apiAccess
|
||||
AND swapId = :swapId
|
||||
|
|
@ -110,13 +124,17 @@ Ev::Io<void> ClaimTxHandler::core_run() {
|
|||
tweak = Secp256k1::PrivKey(r.get<std::string>(0));
|
||||
preimage = Ln::Preimage(r.get<std::string>(1));
|
||||
destinationAddress = r.get<std::string>(2);
|
||||
redeemScript = Util::Str::hexread(
|
||||
claimScript = Util::Str::hexread(
|
||||
r.get<std::string>(3)
|
||||
);
|
||||
timeoutBlockheight = r.get<std::uint32_t>(4);
|
||||
onchainAmount = Ln::Amount::sat(
|
||||
r.get<std::uint64_t>(5)
|
||||
refundScript = Util::Str::hexread(
|
||||
r.get<std::string>(4)
|
||||
);
|
||||
timeoutBlockheight = r.get<std::uint32_t>(5);
|
||||
onchainAmount = Ln::Amount::sat(
|
||||
r.get<std::uint64_t>(6)
|
||||
);
|
||||
refund_pubkey = Secp256k1::PubKey(r.get<std::string>(7));
|
||||
}
|
||||
tx.commit();
|
||||
|
||||
|
|
@ -133,6 +151,38 @@ Ev::Io<void> ClaimTxHandler::core_run() {
|
|||
});
|
||||
}
|
||||
|
||||
/* create aggregate pubkey
|
||||
* role ordered: boltz key #0, our key #1. */
|
||||
try {
|
||||
musigsession = Boltz::MusigSession(tweak, refund_pubkey, signer);
|
||||
} catch (Secp256k1::InvalidPubKey const& e) {
|
||||
return loge(e.what()).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
} catch (Musig::InvalidArg const& e) {
|
||||
return loge(e.what()).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
/* compute script hash (represented as an x-only pubkey) that constitutes
|
||||
* the redeem script for signing the cooperative (key) path of the swap. */
|
||||
auto roothash = compute_root_hash(claimScript, refundScript);
|
||||
|
||||
try {
|
||||
/* apply (add to generator point, muliply by the internal key) the
|
||||
* taptweak hash to the aggregate (internal) key. */
|
||||
musigsession.apply_xonly_tweak(roothash);
|
||||
redeemScript = Bitcoin::pk_to_scriptpk(musigsession.get_output_key());
|
||||
} catch (Musig::InvalidArg const& e) {
|
||||
return loge(e.what()).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
/* Find the outnum from the lockup_tx. */
|
||||
auto outnum = Detail::find_lockup_outnum( lockup_tx
|
||||
, redeemScript
|
||||
|
|
@ -149,10 +199,19 @@ Ev::Io<void> ClaimTxHandler::core_run() {
|
|||
}
|
||||
lockupOut = std::size_t(outnum);
|
||||
|
||||
auto const& lockupoutput = lockup_tx.outputs[lockupOut];
|
||||
|
||||
/* compare locally computed lockscript hash with what Boltz expects. */
|
||||
if (memcmp(redeemScript.data(), lockupoutput.scriptPubKey.data() +2, 32) != 0) {
|
||||
auto msg = std::string("Unexpected lockscript received from Boltz");
|
||||
return loge(msg).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
/* Check the onchain amount is correct. */
|
||||
if ( lockup_tx.outputs[lockupOut].amount
|
||||
!= onchainAmount
|
||||
) {
|
||||
if (lockupoutput.amount != onchainAmount) {
|
||||
auto msg = std::string("Service lockup tx ")
|
||||
+ std::string(lockup_tx) + " "
|
||||
+ "does not pay expected amount."
|
||||
|
|
@ -165,6 +224,7 @@ Ev::Io<void> ClaimTxHandler::core_run() {
|
|||
|
||||
/* Now compute the real preimage from the signer key
|
||||
* and the in-database preimage. */
|
||||
|
||||
real_preimage = Detail::compute_preimage(signer, preimage);
|
||||
|
||||
return Ev::lift();
|
||||
|
|
@ -174,22 +234,132 @@ Ev::Io<void> ClaimTxHandler::core_run() {
|
|||
return env.get_feerate();
|
||||
}).then([this](std::uint32_t feerate) {
|
||||
/* Generate the claim tx. */
|
||||
Detail::initial_claim_tx( claim_tx
|
||||
, lockupClaimFees
|
||||
auto sighash = Detail::initial_claim_tx( claim_tx
|
||||
, lockupClaimFees
|
||||
|
||||
, feerate
|
||||
, blockheight
|
||||
, lockup_txid
|
||||
, lockupOut
|
||||
, onchainAmount
|
||||
, feerate
|
||||
, blockheight
|
||||
, lockup_txid
|
||||
, lockupOut
|
||||
, onchainAmount
|
||||
|
||||
, signer
|
||||
, tweak
|
||||
, real_preimage
|
||||
, redeemScript
|
||||
, redeemScript
|
||||
|
||||
, destinationAddress
|
||||
);
|
||||
, destinationAddress
|
||||
);
|
||||
|
||||
try {
|
||||
musigsession.load_sighash(sighash);
|
||||
|
||||
/* Create our nonce pair for the musig session. */
|
||||
local_pubnonce = musigsession.generate_local_nonces(random);
|
||||
} catch (Musig::InvalidArg const& e) {
|
||||
return loge(e.what()).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
return Ev::lift();
|
||||
}).then([this]() {
|
||||
/* send preimage, public nonce and the unsigned claim tx to Boltz. */
|
||||
auto params = Json::Out()
|
||||
.start_object()
|
||||
.field( "preimage"
|
||||
, std::string(preimage)
|
||||
)
|
||||
.field( "pubNonce"
|
||||
, local_pubnonce
|
||||
)
|
||||
.field( "transaction"
|
||||
, std::string(claim_tx)
|
||||
)
|
||||
.field( "index", 0)
|
||||
.end_object()
|
||||
;
|
||||
|
||||
return conn.api( "/v2/swap/reverse/" + swapId + "/claim"
|
||||
, Util::make_unique<Json::Out>(
|
||||
std::move(params)
|
||||
)
|
||||
);
|
||||
}).then([this](Jsmn::Object res) {
|
||||
/* Parse result. */
|
||||
std::string boltzpubnonce;
|
||||
std::string boltzpartialsig;
|
||||
|
||||
/* we receive from Boltz their partial sig and public nonce in reply. */
|
||||
try {
|
||||
boltzpubnonce = (std::string) res["pubNonce"];
|
||||
boltzpartialsig = (std::string) res["partialSignature"];
|
||||
} catch (Jsmn::TypeError const& e) {
|
||||
auto os = std::ostringstream();
|
||||
os << "Unexpected result from service: "
|
||||
<< res
|
||||
;
|
||||
return loge(os.str()).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
/* Validate pubnonce from Boltz is hex. */
|
||||
if (!Util::Str::ishex(boltzpubnonce)) {
|
||||
return loge( std::string("invalid pubnonce from boltz: ")
|
||||
+ boltzpubnonce
|
||||
).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
/* Validate partial sig from Boltz is hex. */
|
||||
if (!Util::Str::ishex(boltzpartialsig)) {
|
||||
return loge( std::string("invalid partialsig from boltz: ")
|
||||
+ boltzpartialsig
|
||||
).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
std::uint8_t aggsig_buffer[64];
|
||||
auto aggregatesig = Secp256k1::SchnorrSig();
|
||||
try {
|
||||
/* aggregate our public nonce with Boltz's, initiate session. */
|
||||
musigsession.load_pubnonce(boltzpubnonce);
|
||||
musigsession.aggregate_pubnonces();
|
||||
|
||||
/* sign our partial signature and combine into a single sig. */
|
||||
musigsession.load_partial(boltzpartialsig);
|
||||
musigsession.sign_partial();
|
||||
musigsession.verify_part_sigs();
|
||||
musigsession.aggregate_partials(aggsig_buffer);
|
||||
aggregatesig = SchnorrSig::from_buffer(aggsig_buffer);
|
||||
if (!aggregatesig.valid( musigsession.get_output_key()
|
||||
, musigsession.get_sighash() )) {
|
||||
return loge( std::string("invalid aggsig for given sighash and output key")
|
||||
).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
} catch (Musig::InvalidArg const& e) {
|
||||
return loge(e.what()).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
} catch (Secp256k1::InvalidPrivKey const& e) {
|
||||
return loge(e.what()).then([]() {
|
||||
throw End();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
affix_aggregated_signature( claim_tx
|
||||
, real_preimage
|
||||
, redeemScript
|
||||
, aggregatesig.to_buffer()
|
||||
);
|
||||
/* Log it. */
|
||||
auto msg = std::string("Broadcasting claim tx: ")
|
||||
+ std::string(claim_tx)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
#include"Bitcoin/TxId.hpp"
|
||||
#include"Ln/Amount.hpp"
|
||||
#include"Ln/Preimage.hpp"
|
||||
#include"Boltz/Detail/MusigImpl.hpp"
|
||||
#include"Secp256k1/PrivKey.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Sqlite3/Db.hpp"
|
||||
#include<cstdint>
|
||||
#include<functional>
|
||||
|
|
@ -13,6 +15,7 @@
|
|||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
namespace Boltz { class ConnectionIF; }
|
||||
namespace Boltz { class EnvIF; }
|
||||
namespace Ev { template<typename a> class Io; }
|
||||
namespace Secp256k1 { class SignerIF; }
|
||||
|
|
@ -33,6 +36,8 @@ private:
|
|||
Sqlite3::Db db;
|
||||
Boltz::EnvIF& env;
|
||||
std::string api_endpoint;
|
||||
Boltz::ConnectionIF& conn;
|
||||
Secp256k1::Random& random;
|
||||
std::string swapId;
|
||||
std::uint32_t blockheight;
|
||||
|
||||
|
|
@ -43,6 +48,8 @@ private:
|
|||
, Sqlite3::Db db_
|
||||
, Boltz::EnvIF& env_
|
||||
, std::string const& api_endpoint_
|
||||
, Boltz::ConnectionIF& conn_
|
||||
, Secp256k1::Random& random_
|
||||
, std::string const& swapId_
|
||||
, std::uint32_t blockheight_
|
||||
, Bitcoin::Tx lockup_tx_
|
||||
|
|
@ -50,6 +57,8 @@ private:
|
|||
, db(std::move(db_))
|
||||
, env(env_)
|
||||
, api_endpoint(api_endpoint_)
|
||||
, conn(conn_)
|
||||
, random(random_)
|
||||
, swapId(swapId_)
|
||||
, blockheight(blockheight_)
|
||||
, lockup_tx(std::move(lockup_tx_))
|
||||
|
|
@ -64,6 +73,8 @@ public:
|
|||
, Sqlite3::Db db
|
||||
, Boltz::EnvIF& env
|
||||
, std::string const& api_endpoint
|
||||
, Boltz::ConnectionIF& conn
|
||||
, Secp256k1::Random& random
|
||||
, std::string const& swapId
|
||||
, std::uint32_t blockheight
|
||||
, Bitcoin::Tx lockup_tx
|
||||
|
|
@ -73,6 +84,8 @@ public:
|
|||
, std::move(db)
|
||||
, env
|
||||
, api_endpoint
|
||||
, conn
|
||||
, random
|
||||
, swapId
|
||||
, blockheight
|
||||
, std::move(lockup_tx)
|
||||
|
|
@ -94,10 +107,15 @@ private:
|
|||
Ln::Preimage preimage;
|
||||
Ln::Preimage real_preimage;
|
||||
std::string destinationAddress;
|
||||
std::vector<std::uint8_t> redeemScript;
|
||||
Secp256k1::PubKey refund_pubkey;
|
||||
std::uint32_t timeoutBlockheight;
|
||||
Ln::Amount onchainAmount;
|
||||
|
||||
/* Data we generate. */
|
||||
std::vector<std::uint8_t> redeemScript;
|
||||
Boltz::MusigSession musigsession;
|
||||
std::string local_pubnonce;
|
||||
|
||||
/* Data we will store into the table. */
|
||||
std::size_t lockupOut;
|
||||
Ln::Amount lockupClaimFees;
|
||||
|
|
|
|||
47
Boltz/Detail/MusigImpl.hpp
Normal file
47
Boltz/Detail/MusigImpl.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef BOLTZ_MUSIG_IMPL_HPP
|
||||
#define BOLTZ_MUSIG_IMPL_HPP
|
||||
|
||||
#include"Secp256k1/Musig.hpp"
|
||||
#include"Secp256k1/SignerIF.hpp"
|
||||
|
||||
using namespace Secp256k1;
|
||||
|
||||
namespace Boltz {
|
||||
|
||||
class MusigSession : public Secp256k1::Musig::Session {
|
||||
public:
|
||||
Secp256k1::PubKey boltzpub;
|
||||
|
||||
MusigSession() : Secp256k1::Musig::Session() { }
|
||||
|
||||
MusigSession(MusigSession&&) =default;
|
||||
MusigSession& operator= (MusigSession&&) =default;
|
||||
|
||||
/* disallow copys to prevent the secnonce proliferating. */
|
||||
MusigSession(MusigSession const&) =delete;
|
||||
MusigSession& operator=(MusigSession const&) =delete;
|
||||
|
||||
/* Boltz API v2 employs role-based sorting for pubkey
|
||||
* aggregation: their key first, client key second.
|
||||
* We implement Musig::Session ctor and the load_partial
|
||||
* function to reflect that. */
|
||||
MusigSession( Secp256k1::PrivKey const& tweak
|
||||
, Secp256k1::PubKey const& boltzpub_
|
||||
, Secp256k1::SignerIF& signer)
|
||||
: Musig::Session( signer.get_keypair_tweak(tweak)
|
||||
, {boltzpub_, signer.get_pubkey_tweak(tweak)}
|
||||
)
|
||||
, boltzpub(boltzpub_)
|
||||
{ }
|
||||
|
||||
void load_partial(std::string const& psig) {
|
||||
load_partial_at(psig, boltzpub);
|
||||
}
|
||||
void load_pubnonce(std::string const& pnonce) {
|
||||
load_partial_at(pnonce, boltzpub);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* !defined(BOLTZ_MUSIG_IMPL_HPP) */
|
||||
|
|
@ -27,7 +27,7 @@ public:
|
|||
explicit
|
||||
NormalConnection( Ev::ThreadPool& threadpool
|
||||
/* Base address of the API endpoint. */
|
||||
, std::string api_base = "https://boltz.exchange/api"
|
||||
, std::string api_base = "https://api.boltz.exchange"
|
||||
/* SOCKS5 proxy to use. Empty string means no proxy. */
|
||||
, std::string proxy = ""
|
||||
);
|
||||
|
|
|
|||
|
|
@ -94,19 +94,14 @@ Ev::Io<void> ServiceImpl::swap_on_block( std::uint32_t blockheight
|
|||
, std::string swapId
|
||||
) {
|
||||
auto p_swapId = std::make_shared<std::string>(std::move(swapId));
|
||||
auto parms = Json::Out()
|
||||
.start_object()
|
||||
.field("id", *p_swapId)
|
||||
.end_object();
|
||||
return conn->api( "/swapstatus"
|
||||
, Util::make_unique<Json::Out>(std::move(parms))
|
||||
return conn->api( "/swap/" + *p_swapId, nullptr
|
||||
).then([this, p_swapId](Jsmn::Object sres) {
|
||||
/* Save, then log. */
|
||||
auto tmp_sres = std::make_shared<Jsmn::Object>(
|
||||
std::move(sres)
|
||||
);
|
||||
auto os = std::ostringstream();
|
||||
os << "/swapstatus \"" << *p_swapId << "\" => "
|
||||
os << "/swap \"" << *p_swapId << "\" => "
|
||||
<< *tmp_sres
|
||||
;
|
||||
return logd(os.str()).then([tmp_sres]() {
|
||||
|
|
@ -167,6 +162,7 @@ Ev::Io<void> ServiceImpl::swap_on_block( std::uint32_t blockheight
|
|||
return Ev::lift(kont);
|
||||
}
|
||||
|
||||
// TODO TODO check this status exists in v2 API
|
||||
if (status == "transaction.confirmed") {
|
||||
auto txhex = std::string(
|
||||
sres["transaction"]["hex"]
|
||||
|
|
@ -228,6 +224,8 @@ ServiceImpl::swap_onchain( std::shared_ptr<std::string> swapId
|
|||
, db
|
||||
, env
|
||||
, label
|
||||
, *conn
|
||||
, random
|
||||
, *swapId
|
||||
, blockheight
|
||||
, std::move(*tx)
|
||||
|
|
@ -399,4 +397,5 @@ ServiceImpl::get_quotation(Ln::Amount offchainAmount) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -73,21 +73,20 @@ Ev::Io<void> SwapSetupHandler::core_run() {
|
|||
|
||||
auto params = Json::Out()
|
||||
.start_object()
|
||||
.field("type", "reversesubmarine")
|
||||
.field("pairId", "BTC/BTC")
|
||||
.field("orderSide", "buy")
|
||||
.field( "invoiceAmount"
|
||||
, offchainAmount.to_sat()
|
||||
)
|
||||
.field("from", "BTC")
|
||||
.field("to", "BTC")
|
||||
.field( "preimageHash"
|
||||
, std::string(preimageHash)
|
||||
)
|
||||
.field( "claimPublicKey"
|
||||
, std::string(tweakPubKey)
|
||||
)
|
||||
.field( "invoiceAmount"
|
||||
, offchainAmount.to_sat()
|
||||
)
|
||||
.end_object()
|
||||
;
|
||||
return conn.api( "/createswap"
|
||||
return conn.api( "/v2/swap/reverse"
|
||||
, Util::make_unique<Json::Out>(
|
||||
std::move(params)
|
||||
)
|
||||
|
|
@ -95,13 +94,17 @@ Ev::Io<void> SwapSetupHandler::core_run() {
|
|||
}).then([this](Jsmn::Object res) {
|
||||
/* Parse result. */
|
||||
auto tmp_swapId = std::string();
|
||||
auto tmp_s_redeemScript = std::string();
|
||||
auto tmp_s_claimScript = std::string();
|
||||
auto tmp_s_refundScript = std::string();
|
||||
auto tmp_refundPubkey = std::string();
|
||||
auto tmp_invoice = std::string();
|
||||
auto tmp_timeoutBlockheight = std::uint32_t();
|
||||
auto tmp_onchainAmount = std::uint64_t();
|
||||
try {
|
||||
tmp_swapId = (std::string) res["id"];
|
||||
tmp_s_redeemScript = (std::string) res["redeemScript"];
|
||||
tmp_s_claimScript = (std::string) res["swapTree"]["claimLeaf"]["output"];
|
||||
tmp_s_refundScript = (std::string) res["swapTree"]["refundLeaf"]["output"];
|
||||
tmp_refundPubkey = (std::string) res["refundPublicKey"];
|
||||
tmp_invoice = (std::string) res["invoice"];
|
||||
tmp_timeoutBlockheight = (std::uint32_t) (double) res["timeoutBlockHeight"];
|
||||
tmp_onchainAmount = (std::uint64_t) (double) res["onchainAmount"];
|
||||
|
|
@ -115,36 +118,56 @@ Ev::Io<void> SwapSetupHandler::core_run() {
|
|||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
/* Validate redeemScript is hex. */
|
||||
if (!Util::Str::ishex(tmp_s_redeemScript)) {
|
||||
return loge( std::string("invalid redeemScript: ")
|
||||
+ tmp_s_redeemScript
|
||||
/* Validate claimScript is hex. */
|
||||
if (!Util::Str::ishex(tmp_s_claimScript)) {
|
||||
return loge( std::string("invalid claimScript: ")
|
||||
+ tmp_s_claimScript
|
||||
).then([]() {
|
||||
throw Fail();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
auto tmp_redeemScript = Util::Str::hexread(tmp_s_redeemScript);
|
||||
auto tmp_claimScript = Util::Str::hexread(tmp_s_claimScript);
|
||||
|
||||
/* Validate redeemScript is correct. */
|
||||
/* Validate refundScript is hex. */
|
||||
if (!Util::Str::ishex(tmp_s_refundScript)) {
|
||||
return loge( std::string("invalid refundScript: ")
|
||||
+ tmp_s_refundScript
|
||||
).then([]() {
|
||||
throw Fail();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
auto tmp_refundScript = Util::Str::hexread(tmp_s_refundScript);
|
||||
|
||||
/* Validate the two taptree leaf scripts are correct. */
|
||||
auto script_hash160 = Ripemd160::Hash();
|
||||
auto script_mypubkey = Secp256k1::PubKey();
|
||||
auto script_locktime = std::uint32_t();
|
||||
auto script_theirpubkey = Secp256k1::PubKey();
|
||||
auto ok = match_lockscript( script_hash160
|
||||
, script_mypubkey
|
||||
, script_locktime
|
||||
, script_theirpubkey
|
||||
, tmp_redeemScript
|
||||
);
|
||||
ok = ok
|
||||
&& (script_hash160 == preimageHash160)
|
||||
&& (script_mypubkey == tweakPubKey)
|
||||
&& (script_locktime == tmp_timeoutBlockheight)
|
||||
;
|
||||
auto claim_pubkey = Secp256k1::XonlyPubKey();
|
||||
auto ok = match_claimscript( script_hash160
|
||||
, claim_pubkey
|
||||
, tmp_claimScript
|
||||
);
|
||||
ok = ok && (script_hash160 == preimageHash160);
|
||||
|
||||
if (!ok) {
|
||||
return loge( std::string("invalid redeemScript: ")
|
||||
+ tmp_s_redeemScript
|
||||
return loge( std::string("invalid claimScript: ")
|
||||
+ tmp_s_claimScript
|
||||
).then([]() {
|
||||
throw Fail();
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
auto script_locktime = std::uint32_t();
|
||||
auto refund_pubkey = Secp256k1::XonlyPubKey();
|
||||
ok = match_refundscript( script_locktime
|
||||
, refund_pubkey
|
||||
, tmp_refundScript
|
||||
);
|
||||
ok = ok && (script_locktime == tmp_timeoutBlockheight);
|
||||
|
||||
if (!ok) {
|
||||
return loge( std::string("invalid refundScript: ")
|
||||
+ tmp_s_refundScript
|
||||
).then([]() {
|
||||
throw Fail();
|
||||
return Ev::lift();
|
||||
|
|
@ -186,7 +209,9 @@ Ev::Io<void> SwapSetupHandler::core_run() {
|
|||
|
||||
/* Validation is okay! Save the data into the object. */
|
||||
swapId = Util::make_unique<std::string>(std::move(tmp_swapId));
|
||||
redeemScript = std::move(tmp_redeemScript);
|
||||
claimScript = std::move(tmp_claimScript);
|
||||
refundScript = std::move(tmp_refundScript);
|
||||
refundPubkey = std::move(tmp_refundPubkey);
|
||||
timeoutBlockheight = std::move(tmp_timeoutBlockheight);
|
||||
onchainAmount = Ln::Amount::sat(tmp_onchainAmount);
|
||||
invoice = std::move(tmp_invoice);
|
||||
|
|
@ -227,7 +252,9 @@ Ev::Io<void> SwapSetupHandler::core_run() {
|
|||
, preimage
|
||||
, destinationAddress
|
||||
, swapId
|
||||
, redeemScript
|
||||
, claimScript
|
||||
, refundScript
|
||||
, refundPubkey
|
||||
, timeoutBlockheight
|
||||
, onchainAmount
|
||||
, lockedUp
|
||||
|
|
@ -239,7 +266,9 @@ Ev::Io<void> SwapSetupHandler::core_run() {
|
|||
, :preimage
|
||||
, :destinationAddress
|
||||
, :swapId
|
||||
, :redeemScript
|
||||
, :claimScript
|
||||
, :refundScript
|
||||
, :refundPubkey
|
||||
, :timeoutBlockheight
|
||||
, :onchainAmount
|
||||
, :lockedUp
|
||||
|
|
@ -251,11 +280,17 @@ Ev::Io<void> SwapSetupHandler::core_run() {
|
|||
.bind(":preimage", std::string(preimage))
|
||||
.bind(":destinationAddress", destinationAddress)
|
||||
.bind(":swapId", *swapId)
|
||||
.bind(":redeemScript"
|
||||
, Util::Str::hexdump( &redeemScript[0]
|
||||
, redeemScript.size()
|
||||
.bind(":claimScript"
|
||||
, Util::Str::hexdump( &claimScript[0]
|
||||
, claimScript.size()
|
||||
)
|
||||
)
|
||||
.bind(":refundScript"
|
||||
, Util::Str::hexdump( &refundScript[0]
|
||||
, refundScript.size()
|
||||
)
|
||||
)
|
||||
.bind(":refundPubkey", refundPubkey)
|
||||
.bind(":timeoutBlockheight", timeoutBlockheight)
|
||||
.bind(":onchainAmount", onchainAmount.to_sat())
|
||||
.bind(":lockedUp", 0)
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ private:
|
|||
|
||||
/* Data from server. */
|
||||
std::unique_ptr<std::string> swapId;
|
||||
std::vector<std::uint8_t> redeemScript;
|
||||
std::string refundPubkey;
|
||||
std::vector<std::uint8_t> claimScript;
|
||||
std::vector<std::uint8_t> refundScript;
|
||||
std::uint32_t timeoutBlockheight;
|
||||
Ln::Amount onchainAmount;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,16 +7,8 @@
|
|||
namespace Boltz { namespace Detail {
|
||||
|
||||
int find_lockup_outnum( Bitcoin::Tx const& tx
|
||||
, std::vector<std::uint8_t> const& redeemScript
|
||||
, std::vector<std::uint8_t> const& scriptPubKey
|
||||
) {
|
||||
auto hash = Sha256::fun(
|
||||
&redeemScript[0], redeemScript.size()
|
||||
);
|
||||
auto scriptPubKey = std::vector<std::uint8_t>(34);
|
||||
scriptPubKey[0] = 0x00;
|
||||
scriptPubKey[1] = 0x20;
|
||||
hash.to_buffer(&scriptPubKey[2]);
|
||||
|
||||
auto it = std::find_if( tx.outputs.begin(), tx.outputs.end()
|
||||
, [&scriptPubKey](Bitcoin::TxOut const& out) {
|
||||
return out.scriptPubKey == scriptPubKey;
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
#include"Boltz/Detail/initial_claim_tx.hpp"
|
||||
#include"Ln/Amount.hpp"
|
||||
#include"Ln/Preimage.hpp"
|
||||
#include"Secp256k1/Musig.hpp"
|
||||
#include"Secp256k1/PrivKey.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Secp256k1/Signature.hpp"
|
||||
#include"Secp256k1/SignerIF.hpp"
|
||||
#include<sstream>
|
||||
|
||||
namespace {
|
||||
|
|
@ -23,9 +24,11 @@ auto const dust_limit = Ln::Amount::sat(547);
|
|||
|
||||
}
|
||||
|
||||
using namespace Secp256k1;
|
||||
|
||||
namespace Boltz { namespace Detail {
|
||||
|
||||
void
|
||||
Sha256::Hash
|
||||
initial_claim_tx( Bitcoin::Tx& claim_tx /* written by function */
|
||||
, Ln::Amount& claim_tx_fees /* written by function */
|
||||
|
||||
|
|
@ -41,9 +44,6 @@ initial_claim_tx( Bitcoin::Tx& claim_tx /* written by function */
|
|||
, Ln::Amount lockup_amount
|
||||
|
||||
/* Witness details. */
|
||||
, Secp256k1::SignerIF& signer
|
||||
, Secp256k1::PrivKey const& tweak
|
||||
, Ln::Preimage const& preimage
|
||||
, std::vector<std::uint8_t> const& witnessScript
|
||||
|
||||
/* Output address. */
|
||||
|
|
@ -54,7 +54,7 @@ initial_claim_tx( Bitcoin::Tx& claim_tx /* written by function */
|
|||
claim_tx.outputs.resize(1);
|
||||
/* Set nLockTime and nSequence. */
|
||||
claim_tx.nLockTime = blockheight + 1;
|
||||
claim_tx.inputs[0].nSequence = 0xFFFFFFFF; /* Final! Not RBF! */
|
||||
claim_tx.inputs[0].nSequence = 0xFFFFFFFF; /* Final! Not RBF! */ // TODO except we are now in a post Full-RBF world after Bitcoin 28.0...
|
||||
|
||||
/* Set up input. */
|
||||
claim_tx.inputs[0].prevTxid = lockup_txid;
|
||||
|
|
@ -69,9 +69,10 @@ initial_claim_tx( Bitcoin::Tx& claim_tx /* written by function */
|
|||
);
|
||||
|
||||
/* Measure weight. */
|
||||
//TODO value of sig varint and size of signature itself must be schnorr/musig based
|
||||
auto nonwitness_weight = get_nonwitness_weight(claim_tx);
|
||||
auto witness_weight = 1 /* varint of signature */
|
||||
+ 73 /* DER-encoded ECDSA signature. */
|
||||
+ 64 /* schnorr signature. */
|
||||
+ 1 /* varint of preimage */
|
||||
+ 32 /* preimage */
|
||||
+ 1 /* varint of witnessScript */
|
||||
|
|
@ -110,23 +111,39 @@ initial_claim_tx( Bitcoin::Tx& claim_tx /* written by function */
|
|||
* for now.
|
||||
* That took several hours of spinning....
|
||||
*/
|
||||
auto scriptCode = std::vector<std::uint8_t>(witnessScript.size() + 1);
|
||||
scriptCode[0] = std::uint8_t(witnessScript.size());
|
||||
auto scriptPubkey = std::vector<std::uint8_t>(witnessScript.size() + 1);
|
||||
scriptPubkey[0] = std::uint8_t(witnessScript.size());
|
||||
std::copy( witnessScript.begin(), witnessScript.end()
|
||||
, scriptCode.begin() + 1
|
||||
, scriptPubkey.begin() + 1
|
||||
);
|
||||
auto sighash = Bitcoin::sighash( claim_tx
|
||||
, Bitcoin::SIGHASH_ALL
|
||||
, 0
|
||||
, lockup_amount
|
||||
, scriptCode
|
||||
);
|
||||
auto signature = signer.get_signature_tweak(tweak, sighash);
|
||||
|
||||
/* Load up the witnesses. */
|
||||
return Bitcoin::p2tr_sighash
|
||||
( claim_tx
|
||||
, Bitcoin::SIGHASH_DEFAULT
|
||||
, 0
|
||||
, std::vector<Ln::Amount>{lockup_amount}
|
||||
, std::vector<std::vector<std::uint8_t>>{scriptPubkey}
|
||||
, Bitcoin::KEYPATH
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
affix_aggregated_signature( Bitcoin::Tx& claim_tx
|
||||
/* Witness details. */
|
||||
, Ln::Preimage const& preimage
|
||||
, std::vector<std::uint8_t> const& witnessScript
|
||||
, std::vector<std::uint8_t> const& aggregatesig
|
||||
) {
|
||||
auto scriptPubkey = std::vector<std::uint8_t>(witnessScript.size() + 1);
|
||||
scriptPubkey[0] = std::uint8_t(witnessScript.size());
|
||||
std::copy( witnessScript.begin(), witnessScript.end()
|
||||
, scriptPubkey.begin() + 1
|
||||
);
|
||||
|
||||
/* Load up the witnesses. */ // TODO structured the same in bip341 ?
|
||||
auto& witnesses = claim_tx.inputs[0].witness.witnesses;
|
||||
witnesses[0] = signature.der_encode();
|
||||
witnesses[0].push_back(std::uint8_t(Bitcoin::SIGHASH_ALL));
|
||||
witnesses[0] = aggregatesig;
|
||||
witnesses[0].push_back(std::uint8_t(Bitcoin::SIGHASH_DEFAULT));
|
||||
witnesses[1].resize(32);
|
||||
preimage.to_buffer(&witnesses[1][0]);
|
||||
witnesses[2] = witnessScript;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef BOLTZ_DETAIL_INITIAL_CLAIM_TX_HPP
|
||||
#define BOLTZ_DETAIL_INITIAL_CLAIM_TX_HPP
|
||||
|
||||
#include"Sha256/Hash.hpp"
|
||||
#include<cstdint>
|
||||
#include<string>
|
||||
#include<vector>
|
||||
|
|
@ -9,8 +10,6 @@ namespace Bitcoin { class Tx; }
|
|||
namespace Bitcoin { class TxId; }
|
||||
namespace Ln { class Amount; }
|
||||
namespace Ln { class Preimage; }
|
||||
namespace Secp256k1 { class PrivKey; }
|
||||
namespace Secp256k1 { class SignerIF; }
|
||||
|
||||
namespace Boltz { namespace Detail {
|
||||
|
||||
|
|
@ -20,7 +19,7 @@ namespace Boltz { namespace Detail {
|
|||
* for a reverse submarine (offchain-to-onchain)
|
||||
* swap.
|
||||
*/
|
||||
void
|
||||
Sha256::Hash
|
||||
initial_claim_tx( Bitcoin::Tx& claim_tx /* written by function */
|
||||
, Ln::Amount& claim_tx_fees /* written by function */
|
||||
|
||||
|
|
@ -35,16 +34,22 @@ initial_claim_tx( Bitcoin::Tx& claim_tx /* written by function */
|
|||
, std::uint32_t lockup_outnum
|
||||
, Ln::Amount lockup_amount
|
||||
|
||||
/* Witness details. */
|
||||
, Secp256k1::SignerIF& signer
|
||||
, Secp256k1::PrivKey const& tweak
|
||||
, Ln::Preimage const& preimage
|
||||
/* Witness script. */
|
||||
, std::vector<std::uint8_t> const& witnessScript
|
||||
|
||||
/* Output address. */
|
||||
, std::string const& output_addr
|
||||
);
|
||||
|
||||
void
|
||||
affix_aggregated_signature(Bitcoin::Tx& claim_tx /* written by function */
|
||||
/* Arguments to the function. */
|
||||
|
||||
/* Witness details. */
|
||||
, Ln::Preimage const& preimage
|
||||
, std::vector<std::uint8_t> const& witnessScript
|
||||
, std::vector<std::uint8_t> const& aggregatesig
|
||||
);
|
||||
}}
|
||||
|
||||
#endif /* !defined(BOLTZ_DETAIL_INITIAL_CLAIM_TX_HPP) */
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
namespace {
|
||||
|
||||
/* Thrown by below if eof or unmatche. */
|
||||
/* Thrown by below if eof or unmatched. */
|
||||
struct Error { };
|
||||
|
||||
/* Reads from the given sequence, throws
|
||||
|
|
@ -43,73 +43,41 @@ Reader<It> make_reader(It b, It e) {
|
|||
}
|
||||
|
||||
namespace Boltz { namespace Detail {
|
||||
|
||||
bool match_lockscript( Ripemd160::Hash& hash
|
||||
, Secp256k1::PubKey& pubkey_hash
|
||||
, std::uint32_t& locktime
|
||||
, Secp256k1::PubKey& pubkey_locktime
|
||||
, std::vector<std::uint8_t> const& script
|
||||
) {
|
||||
std::uint8_t buf[33];
|
||||
auto r = make_reader(script.begin(), script.end());
|
||||
/* Oh no, the try-if antipattern!
|
||||
* This still ends up more succinct than using `if`
|
||||
* over and over again, however.
|
||||
*/
|
||||
bool match_claimscript( Ripemd160::Hash& hash
|
||||
, Secp256k1::XonlyPubKey& claim_pubkey
|
||||
, std::vector<std::uint8_t> const& script
|
||||
) {
|
||||
std::uint8_t buf[32];
|
||||
auto r = make_reader(script.begin(), script.end());
|
||||
|
||||
try {
|
||||
/* Template
|
||||
/* claim script Template
|
||||
82 OP_SIZE
|
||||
01 20 push(32)
|
||||
87 OP_EQUAL
|
||||
63 OP_IF
|
||||
a9 OP_HASH160
|
||||
14 (20bytes) push(RIPEMD160(hash))
|
||||
88 OP_EQUALVERIFY
|
||||
21 (33bytes) push(pubkey_hash)
|
||||
67 OP_ELSE
|
||||
75 OP_DROP
|
||||
03 (3 bytes little-endian) push(locktime)
|
||||
b1 OP_CHECKLOCKTIMEVERIFY
|
||||
75 OP_DROP
|
||||
21 (33 bytes) push(pubkey_locktime)
|
||||
68 OP_ENDIF
|
||||
88 OP_EQUALVERIFY
|
||||
a9 OP_HASH160
|
||||
14 (20bytes) push(RIPEMD160(hash))
|
||||
88 OP_EQUALVERIFY
|
||||
20 (32bytes) push(claim_pubkey)
|
||||
ac OP_CHECKSIG
|
||||
*/
|
||||
r.expect(0x82);
|
||||
r.expect(0x01); r.expect(0x20);
|
||||
r.expect(0x87);
|
||||
r.expect(0x63);
|
||||
r.expect(0x88);
|
||||
r.expect(0xa9);
|
||||
r.expect(0x14);
|
||||
for (auto i = std::size_t(0); i < 20; ++i)
|
||||
buf[i] = r.get();
|
||||
hash.from_buffer(buf);
|
||||
r.expect(0x88);
|
||||
r.expect(0x21);
|
||||
for (auto i = std::size_t(0); i < 33; ++i)
|
||||
r.expect(0x20);
|
||||
for (auto i = std::size_t(0); i < 32; ++i)
|
||||
buf[i] = r.get();
|
||||
pubkey_hash = Secp256k1::PubKey::from_buffer(buf);
|
||||
r.expect(0x67);
|
||||
r.expect(0x75);
|
||||
/* Always 3 bytes?
|
||||
* 1->2 bytes would only work for very young blockchains.
|
||||
* 3 bytes would work for blockchains of at least 65,536
|
||||
* to 16,777,215 blocks, which is a fairly large range.
|
||||
*/
|
||||
r.expect(0x03);
|
||||
for (auto i = std::size_t(0); i < 3; ++i)
|
||||
buf[i] = r.get();
|
||||
locktime = (std::uint32_t(buf[0]) << 0)
|
||||
| (std::uint32_t(buf[1]) << 8)
|
||||
| (std::uint32_t(buf[2]) << 16)
|
||||
;
|
||||
r.expect(0xb1);
|
||||
r.expect(0x75);
|
||||
r.expect(0x21);
|
||||
for (auto i = std::size_t(0); i < 33; ++i)
|
||||
buf[i] = r.get();
|
||||
pubkey_locktime = Secp256k1::PubKey::from_buffer(buf);
|
||||
r.expect(0x68);
|
||||
claim_pubkey = Secp256k1::XonlyPubKey::from_buffer(buf);
|
||||
r.expect(0xac);
|
||||
|
||||
r.expect_end();
|
||||
|
|
@ -123,4 +91,48 @@ bool match_lockscript( Ripemd160::Hash& hash
|
|||
}
|
||||
}
|
||||
|
||||
bool match_refundscript( std::uint32_t& locktime
|
||||
, Secp256k1::XonlyPubKey& refund_pubkey
|
||||
, std::vector<std::uint8_t> const& script
|
||||
) {
|
||||
std::uint8_t buf[32];
|
||||
auto r = make_reader(script.begin(), script.end());
|
||||
|
||||
try {
|
||||
/* refund script Template
|
||||
20 (32bytes) push(pubkey_hash)
|
||||
ad OP_CHECKSIGVERIFY
|
||||
03 (3 bytes little-endian) push(locktime)
|
||||
b1 OP_CHECKLOCKTIMEVERIFY
|
||||
*/
|
||||
r.expect(0x20);
|
||||
for (auto i = std::size_t(0); i < 32; ++i)
|
||||
buf[i] = r.get();
|
||||
refund_pubkey = Secp256k1::XonlyPubKey::from_buffer(buf);
|
||||
r.expect(0xad);
|
||||
/* Always 3 bytes?
|
||||
* 1->2 bytes would only work for very young blockchains.
|
||||
* 3 bytes would work for blockchains of at least 65,536
|
||||
* to 16,777,215 blocks, which is a fairly large range.
|
||||
*/
|
||||
r.expect(0x03);
|
||||
for (auto i = std::size_t(0); i < 3; ++i)
|
||||
buf[i] = r.get();
|
||||
locktime = (std::uint32_t(buf[0]) << 0)
|
||||
| (std::uint32_t(buf[1]) << 8)
|
||||
| (std::uint32_t(buf[2]) << 16)
|
||||
;
|
||||
r.expect(0xb1);
|
||||
|
||||
r.expect_end();
|
||||
return true;
|
||||
} catch (Error const&) {
|
||||
/* Some problem. */
|
||||
return false;
|
||||
} catch (Secp256k1::InvalidPubKey const&) {
|
||||
/* Invalid pubkey. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,29 @@
|
|||
#include<vector>
|
||||
|
||||
namespace Ripemd160 { class Hash; }
|
||||
namespace Secp256k1 { class PubKey; }
|
||||
namespace Secp256k1 { class XonlyPubKey; }
|
||||
|
||||
namespace Boltz { namespace Detail {
|
||||
|
||||
/** Boltz::Detail::match_lockscript
|
||||
*
|
||||
/** Boltz::Detail::match_claimscript
|
||||
* @brief determines if the given supposed SCRIPT
|
||||
* matches the expected lockscript from a proper
|
||||
* matches the expected tapscript leaf from a proper
|
||||
* BOLTZ instance.
|
||||
*/
|
||||
bool match_lockscript( Ripemd160::Hash& hash
|
||||
, Secp256k1::PubKey& pubkey_hash
|
||||
, std::uint32_t& locktime
|
||||
, Secp256k1::PubKey& pubkey_locktime
|
||||
|
||||
bool match_claimscript( Ripemd160::Hash& hash
|
||||
, Secp256k1::XonlyPubKey&
|
||||
, std::vector<std::uint8_t> const& script
|
||||
);
|
||||
|
||||
/** Boltz::Detail::match_refundscript
|
||||
* @brief determines if the given supposed SCRIPT
|
||||
* matches the expected tapscript leaf from a proper
|
||||
* BOLTZ instance.
|
||||
*/
|
||||
|
||||
bool match_refundscript( std::uint32_t& locktime
|
||||
, Secp256k1::XonlyPubKey&
|
||||
, std::vector<std::uint8_t> const& script
|
||||
);
|
||||
|
||||
|
|
|
|||
23
Boltz/Detail/swaptree.cpp
Normal file
23
Boltz/Detail/swaptree.cpp
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#include"Boltz/Detail/swaptree.hpp"
|
||||
#include"Secp256k1/TapscriptTree.hpp"
|
||||
|
||||
using namespace Secp256k1;
|
||||
|
||||
namespace Boltz { namespace Detail {
|
||||
|
||||
std::vector<std::uint8_t> compute_root_hash
|
||||
( std::vector<std::uint8_t> const& claimscript
|
||||
, std::vector<std::uint8_t> const& refundscript
|
||||
) {
|
||||
/* leaves to buffers */
|
||||
auto leaf0 = TapTree::encoded_leaf(claimscript);
|
||||
auto leaf1 = TapTree::encoded_leaf(refundscript);
|
||||
|
||||
/* tagged hashes of leaves */
|
||||
auto leafhashes = TapTree::LeafPair(leaf0, leaf1);
|
||||
|
||||
/* combine leaves into a branch */
|
||||
return leafhashes.compute_branch_hash();
|
||||
}
|
||||
|
||||
}}
|
||||
23
Boltz/Detail/swaptree.hpp
Normal file
23
Boltz/Detail/swaptree.hpp
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef BOLTZ_DETAIL_SWAP_TREE_HPP
|
||||
#define BOLTZ_DETAIL_SWAP_TREE_HPP
|
||||
|
||||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
namespace Secp256k1 { namespace Musig { class AggPubKey; } }
|
||||
namespace Secp256k1 { namespace Musig { class TapTweak; } }
|
||||
|
||||
namespace Boltz { namespace Detail {
|
||||
|
||||
/** Boltz::Detail::match_lockscript
|
||||
* TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
|
||||
* @brief .
|
||||
*/
|
||||
|
||||
std::vector<std::uint8_t> compute_root_hash
|
||||
( std::vector<std::uint8_t> const&
|
||||
, std::vector<std::uint8_t> const& );
|
||||
|
||||
}}
|
||||
|
||||
#endif /* !defined(BOLTZ_DETAIL_SWAP_TREE_HPP) */
|
||||
|
|
@ -40,6 +40,7 @@ private:
|
|||
return do_initialize();
|
||||
});
|
||||
}
|
||||
/** TODO migrate old db? new table? **/
|
||||
Ev::Io<void> do_initialize() {
|
||||
/* Perform actual initialization. */
|
||||
return db.transact().then([this](Sqlite3::Tx tx) {
|
||||
|
|
@ -68,7 +69,9 @@ private:
|
|||
-- from exchange, before paying invoice.
|
||||
-- id from exchange.
|
||||
, swapId TEXT NOT NULL
|
||||
, redeemScript TEXT NOT NULL
|
||||
, claimScript TEXT NOT NULL
|
||||
, refundScript TEXT NOT NULL
|
||||
, refundPubkey TEXT NOT NULL
|
||||
, timeoutBlockheight INTEGER NOT NULL
|
||||
-- in satoshi.
|
||||
, onchainAmount INTEGER NOT NULL
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ auto const boltz_instances = std::map< Boss::Msg::Network
|
|||
, ""
|
||||
, "http://jsyqqszgfrya6nj7nhi4hu4tdpuvfursl7dyxeiukzit5mvckqbzxpad.onion"
|
||||
}
|
||||
, { "Boltz_API_v2_mainnet"
|
||||
, "https://api.boltz.exchange"
|
||||
, "http://boltzzzbnus4m7mta3cxmflnps4fp7dueu2tgurstbvrbt6xswzcocyd.onion/api"
|
||||
}
|
||||
}
|
||||
}
|
||||
, { Boss::Msg::Network_Testnet
|
||||
|
|
@ -30,6 +34,10 @@ auto const boltz_instances = std::map< Boss::Msg::Network
|
|||
, "https://testnet.boltz.exchange/api"
|
||||
, "http://tboltzzrsoc3npe6sydcrh37mtnfhnbrilqi45nao6cgc6dr7n2eo3id.onion/api"
|
||||
}
|
||||
, { "Boltz_API_v2_testnet3"
|
||||
, "https://api.testnet.boltz.exchange"
|
||||
, "http://boltzzzbnus4m7mta3cxmflnps4fp7dueu2tgurstbvrbt6xswzcocyd.onion/api"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,10 +32,12 @@ void NewaddrHandler::start() {
|
|||
});
|
||||
}
|
||||
Ev::Io<void> NewaddrHandler::newaddr(void* requester) {
|
||||
return rpc->command( "newaddr"
|
||||
/** BREAKING CHANGE:
|
||||
* "newaddr p2tr" NOT compatible with v23.05 and older */
|
||||
return rpc->command( "newaddr p2tr"
|
||||
, Json::Out::empty_object()
|
||||
).then([this, requester](Jsmn::Object res) {
|
||||
auto addr = std::string(res["bech32"]);
|
||||
auto addr = std::string(res["p2tr"]);
|
||||
return bus.raise(Msg::ResponseNewaddr{
|
||||
std::move(addr), requester
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include"Net/Fd.hpp"
|
||||
#include"Secp256k1/PrivKey.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Secp256k1/KeyPair.hpp"
|
||||
#include"Secp256k1/Signature.hpp"
|
||||
#include"Secp256k1/SignerIF.hpp"
|
||||
#include"Sha256/Hash.hpp"
|
||||
|
|
@ -54,8 +55,14 @@ public:
|
|||
basicsecure_clear(buf, sizeof(buf));
|
||||
return std::move(hasher).finalize();
|
||||
}
|
||||
Secp256k1::KeyPair
|
||||
get_keypair_tweak(Secp256k1::PrivKey const& tweak
|
||||
) override {
|
||||
return Secp256k1::KeyPair(tweak * sk);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void unlink_noerr(std::string const& filename) {
|
||||
auto my_errno = errno;
|
||||
unlink(filename.c_str());
|
||||
|
|
|
|||
14
Makefile.am
14
Makefile.am
|
|
@ -24,6 +24,8 @@ libclboss_la_SOURCES = \
|
|||
Bitcoin/WitnessField.hpp \
|
||||
Bitcoin/addr_to_scriptPubKey.cpp \
|
||||
Bitcoin/addr_to_scriptPubKey.hpp \
|
||||
Bitcoin/pubkey_to_scriptPubKey.cpp \
|
||||
Bitcoin/pubkey_to_scriptPubKey.hpp \
|
||||
Bitcoin/hash160.cpp \
|
||||
Bitcoin/hash160.hpp \
|
||||
Bitcoin/le.cpp \
|
||||
|
|
@ -38,6 +40,7 @@ libclboss_la_SOURCES = \
|
|||
Boltz/Detail/ClaimTxHandler.hpp \
|
||||
Boltz/Detail/FallbackConnection.cpp \
|
||||
Boltz/Detail/FallbackConnection.hpp \
|
||||
Boltz/Detail/MusigImpl.hpp \
|
||||
Boltz/Detail/NormalConnection.cpp \
|
||||
Boltz/Detail/NormalConnection.hpp \
|
||||
Boltz/Detail/NullConnection.cpp \
|
||||
|
|
@ -46,6 +49,7 @@ libclboss_la_SOURCES = \
|
|||
Boltz/Detail/ServiceImpl.hpp \
|
||||
Boltz/Detail/SwapSetupHandler.cpp \
|
||||
Boltz/Detail/SwapSetupHandler.hpp \
|
||||
Boltz/Detail/MusigImpl.hpp \
|
||||
Boltz/Detail/compute_preimage.cpp \
|
||||
Boltz/Detail/compute_preimage.hpp \
|
||||
Boltz/Detail/create_connection.cpp \
|
||||
|
|
@ -56,6 +60,8 @@ libclboss_la_SOURCES = \
|
|||
Boltz/Detail/initial_claim_tx.hpp \
|
||||
Boltz/Detail/match_lockscript.cpp \
|
||||
Boltz/Detail/match_lockscript.hpp \
|
||||
Boltz/Detail/swaptree.cpp \
|
||||
Boltz/Detail/swaptree.hpp \
|
||||
Boltz/EnvIF.hpp \
|
||||
Boltz/Service.hpp \
|
||||
Boltz/ServiceFactory.cpp \
|
||||
|
|
@ -483,6 +489,8 @@ libclboss_la_SOURCES = \
|
|||
Secp256k1/G.cpp \
|
||||
Secp256k1/G.hpp \
|
||||
Secp256k1/KeyPair.hpp \
|
||||
Secp256k1/Musig.cpp \
|
||||
Secp256k1/Musig.hpp \
|
||||
Secp256k1/PrivKey.cpp \
|
||||
Secp256k1/PrivKey.hpp \
|
||||
Secp256k1/PubKey.cpp \
|
||||
|
|
@ -492,6 +500,10 @@ libclboss_la_SOURCES = \
|
|||
Secp256k1/Signature.cpp \
|
||||
Secp256k1/Signature.hpp \
|
||||
Secp256k1/SignerIF.hpp \
|
||||
Secp256k1/TapscriptTree.cpp \
|
||||
Secp256k1/TapscriptTree.hpp \
|
||||
Secp256k1/tagged_hashes.cpp \
|
||||
Secp256k1/tagged_hashes.hpp \
|
||||
Sha256/Hash.cpp \
|
||||
Sha256/Hash.hpp \
|
||||
Sha256/Hasher.cpp \
|
||||
|
|
@ -641,6 +653,8 @@ TESTS = \
|
|||
tests/s/test_bus \
|
||||
tests/sha256/test_hash \
|
||||
tests/sha256/test_hasher \
|
||||
tests/secp256k1/bip327 \
|
||||
tests/secp256k1/bip341sighash \
|
||||
tests/sqlite3/test_sqlite3 \
|
||||
tests/stats/test_reservoir_sampler \
|
||||
tests/stats/test_running_mean \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#include<string.h>
|
||||
#include<memory>
|
||||
#include<type_traits>
|
||||
#include<secp256k1.h>
|
||||
#include"Secp256k1/G.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
|
|
@ -8,7 +10,7 @@
|
|||
namespace {
|
||||
|
||||
std::uint8_t g[33] = {
|
||||
0x02,
|
||||
0x02, /* <-- this byte encodes (even|odd)ness for 33 byte keys only */
|
||||
0x79, 0xBE, 0x66, 0x7E,
|
||||
0xF9, 0xDC, 0xBB, 0xAC,
|
||||
0x55, 0xA0, 0x62, 0x95,
|
||||
|
|
@ -19,7 +21,8 @@ std::uint8_t g[33] = {
|
|||
0x16, 0xF8, 0x17, 0x98
|
||||
};
|
||||
|
||||
Secp256k1::PubKey make_g() {
|
||||
template <typename P>
|
||||
P make_g() {
|
||||
/* Create a temporary context for ourself; we cannot be
|
||||
* certain that Secp256k1::Detail::context has been
|
||||
* properly initialized yet!
|
||||
|
|
@ -31,13 +34,28 @@ Secp256k1::PubKey make_g() {
|
|||
auto handler = std::shared_ptr<secp256k1_context_struct>( ctx
|
||||
, &secp256k1_context_destroy
|
||||
);
|
||||
return Secp256k1::PubKey::from_buffer_with_context(handler.get(), g);
|
||||
bool tiebreaker;
|
||||
if constexpr (std::is_same_v<P, Secp256k1::PubKey>)
|
||||
tiebreaker = true;
|
||||
else if (std::is_same_v<P, Secp256k1::XonlyPubKey>)
|
||||
tiebreaker = false;
|
||||
else
|
||||
throw Secp256k1::InvalidPubKey();
|
||||
|
||||
/* the 0x02 zero-th byte is not part of the value, it indicates
|
||||
* whether a point is even or odd for 33 byte keys (described
|
||||
* in the bips as the "tie-breaker" byte). we discard it
|
||||
* for 32 byte x-only keys, which are always even. */
|
||||
return P::from_buffer_with_context( handler.get()
|
||||
, &g[ (tiebreaker ? 0 : 1) ]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Secp256k1 {
|
||||
|
||||
PubKey G = make_g();
|
||||
PubKey G_tied = make_g<Secp256k1::PubKey>();
|
||||
XonlyPubKey G_xcoord = make_g<Secp256k1::XonlyPubKey>();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
#define SECP256K1_G_HPP
|
||||
|
||||
namespace Secp256k1 { class PubKey; }
|
||||
namespace Secp256k1 { extern PubKey G; }
|
||||
namespace Secp256k1 { class XonlyPubKey; }
|
||||
namespace Secp256k1 { extern PubKey G_tied; }
|
||||
namespace Secp256k1 { extern XonlyPubKey G_xcoord; }
|
||||
|
||||
#endif /* SECP256K1_G_HPP */
|
||||
|
|
|
|||
514
Secp256k1/Musig.cpp
Normal file
514
Secp256k1/Musig.cpp
Normal file
|
|
@ -0,0 +1,514 @@
|
|||
#include"Secp256k1/Musig.hpp"
|
||||
#include"Secp256k1/PrivKey.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Secp256k1/Random.hpp"
|
||||
#include"Secp256k1/Detail/context.hpp"
|
||||
#include"Secp256k1/tagged_hashes.hpp"
|
||||
#include"Util/make_unique.hpp"
|
||||
#include"Util/Str.hpp"
|
||||
#include<basicsecure.h>
|
||||
#include<secp256k1.h>
|
||||
#include<secp256k1_musig.h>
|
||||
#include<assert.h>
|
||||
#include<stdlib.h>
|
||||
#include<string.h>
|
||||
#include<sstream>
|
||||
|
||||
using Secp256k1::Detail::context;
|
||||
|
||||
namespace Secp256k1 { namespace Musig {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Signatory {
|
||||
Secp256k1::PubKey public_key;
|
||||
std::uint8_t pubnonce[132]{}; /* secp256k1_musig_pubnonce */
|
||||
std::uint8_t part_sig[36]{}; /* secp256k1_musig_partial_sig */
|
||||
bool pubnonce_loaded{false};
|
||||
bool partial_loaded{false};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class Session::Impl {
|
||||
private:
|
||||
std::vector<Signatory> signers;
|
||||
Signatory& local;
|
||||
std::uint8_t signing_key[32];
|
||||
XonlyPubKey aggregate_pk;
|
||||
secp256k1_musig_keyagg_cache aggkey_cache{};
|
||||
XonlyPubKey output_key;
|
||||
std::uint8_t sighash[32];
|
||||
// TODO should we mlock the memory allocated for secnonce? does mlock
|
||||
// port to other unix clones?
|
||||
secp256k1_musig_secnonce secnonce{};
|
||||
secp256k1_musig_aggnonce aggnonce{};
|
||||
secp256k1_musig_session session{};
|
||||
bool sighash_loaded{false};
|
||||
|
||||
public:
|
||||
Impl(Impl&&) =default;
|
||||
Impl& operator=(Impl&&) =default;
|
||||
|
||||
Impl( std::vector<Signatory> signers_
|
||||
, Signatory& local_
|
||||
, Secp256k1::KeyPair const& keys
|
||||
) : signers(std::move(signers_))
|
||||
, local(local_)
|
||||
{
|
||||
/* copy signing key. */
|
||||
keys.priv().to_buffer(signing_key);
|
||||
|
||||
const secp256k1_pubkey *pkarr[signers.size()];
|
||||
|
||||
for (size_t i{0}; i < signers.size(); ++i)
|
||||
pkarr[i] = reinterpret_cast<const secp256k1_pubkey*>(signers[i].public_key.get_key());
|
||||
|
||||
aggregate_pk = pubkey_aggregate( &aggkey_cache
|
||||
, pkarr
|
||||
, signers.size()
|
||||
);
|
||||
}
|
||||
|
||||
/* aggregate local/external pubkeys, and (if not nullptr),
|
||||
* initialize given secp256k1 musig aggkey cache. */
|
||||
static
|
||||
XonlyPubKey pubkey_aggregate( secp256k1_musig_keyagg_cache *agg_cache
|
||||
, const secp256k1_pubkey **pkarr
|
||||
, size_t nKeys
|
||||
) {
|
||||
std::uint8_t xkey_buf[64];
|
||||
auto res = secp256k1_musig_pubkey_agg
|
||||
( context.get()
|
||||
, reinterpret_cast<secp256k1_xonly_pubkey*>(xkey_buf)
|
||||
, agg_cache
|
||||
, pkarr
|
||||
, nKeys
|
||||
);
|
||||
|
||||
if (!res)
|
||||
throw InvalidArg("Session::Impl::pubkey_aggregate secp256k1_musig_pubkey_agg");
|
||||
|
||||
std::uint8_t serialized_xkey[32];
|
||||
res = secp256k1_xonly_pubkey_serialize
|
||||
( context.get()
|
||||
, reinterpret_cast<unsigned char*>(serialized_xkey)
|
||||
, reinterpret_cast<const secp256k1_xonly_pubkey*>(xkey_buf)
|
||||
);
|
||||
|
||||
return XonlyPubKey::from_buffer(serialized_xkey);
|
||||
}
|
||||
|
||||
void serialize_aggkey(std::uint8_t buf[32]) {
|
||||
aggregate_pk.to_buffer(buf);
|
||||
}
|
||||
|
||||
void
|
||||
xonly_tweak( std::uint8_t aggkey_serialized[32]
|
||||
, std::vector<std::uint8_t> const& scripthash ) {
|
||||
unsigned char msg[64];
|
||||
memcpy(&msg[0], &aggkey_serialized[0], 32);
|
||||
memcpy(&msg[32], scripthash.data(), 32);
|
||||
|
||||
unsigned char tweak[32];
|
||||
Secp256k1::tagged_hash( tweak
|
||||
, msg
|
||||
, std::size_t{64}
|
||||
, Tag::TWEAK );
|
||||
|
||||
secp256k1_pubkey ecpk;
|
||||
int res = secp256k1_musig_pubkey_xonly_tweak_add
|
||||
( context.get()
|
||||
, &ecpk
|
||||
, &aggkey_cache
|
||||
, reinterpret_cast<const unsigned char*>(tweak)
|
||||
);
|
||||
if (!res)
|
||||
throw InvalidArg("Tweak::Impl::tweak_musig secp256k1_musig_pubkey_xonly_tweak_add");
|
||||
|
||||
output_key = XonlyPubKey::from_ecdsa_pk(reinterpret_cast<const std::uint8_t*>(&ecpk));
|
||||
}
|
||||
|
||||
void load_sighash(Sha256::Hash const& sh) {
|
||||
sh.to_buffer(sighash);
|
||||
sighash_loaded = true;
|
||||
}
|
||||
|
||||
std::string
|
||||
generate_local_nonces(Secp256k1::Random& random) {
|
||||
if (!sighash_loaded)
|
||||
throw InvalidArg("Session::generate_local_nonces sighash not loaded");
|
||||
|
||||
unsigned char secrand[32];
|
||||
for (auto i{0}; i < 32; ++i)
|
||||
secrand[i] = random.get();
|
||||
|
||||
unsigned char extra32[32];
|
||||
for (auto i{0}; i < 32; ++i)
|
||||
extra32[i] = random.get();
|
||||
|
||||
auto *p_nonce = reinterpret_cast<secp256k1_musig_pubnonce*>(&(local.pubnonce));
|
||||
auto res = secp256k1_musig_nonce_gen
|
||||
( context.get()
|
||||
// TODO should we mlock the memory allocated for secnonce? does mlock
|
||||
// port to other unix clones?
|
||||
, &secnonce
|
||||
, p_nonce
|
||||
, secrand
|
||||
, signing_key
|
||||
, reinterpret_cast<const secp256k1_pubkey*>(local.public_key.get_key())
|
||||
, reinterpret_cast<const unsigned char*>(sighash) /* msg32 */
|
||||
, reinterpret_cast<const secp256k1_musig_keyagg_cache*>(&aggkey_cache)
|
||||
// FIXME unclear on what's best practice, but if the rng is deficient, why use it twice?
|
||||
, reinterpret_cast<const unsigned char*>(extra32) /* extra32 */
|
||||
);
|
||||
|
||||
if (!res)
|
||||
throw InvalidArg("Session::generate_local_nonces secp256k1_musig_nonce_gen");
|
||||
|
||||
local.pubnonce_loaded = true;
|
||||
return dump_pubnonce(p_nonce);
|
||||
}
|
||||
|
||||
void load_pubnonce_at( std::string const& pubnonce
|
||||
, Signatory& signer ) {
|
||||
auto buf = Util::Str::hexread(pubnonce);
|
||||
if (buf.size() != 66) /* <-- size of a serialized pubnonce. */
|
||||
throw InvalidArg("bad size secp256k1_musig_pubnonce");
|
||||
|
||||
auto res = secp256k1_musig_pubnonce_parse
|
||||
( context.get()
|
||||
, reinterpret_cast<secp256k1_musig_pubnonce*>(&(signer.pubnonce))
|
||||
, buf.data()
|
||||
);
|
||||
|
||||
if (res != 1)
|
||||
throw InvalidArg("Session::Impl::Impl secp256k1_musig_pubnonce_parse");
|
||||
|
||||
signer.pubnonce_loaded = true;
|
||||
}
|
||||
|
||||
/* aggregate our public nonce with the counterpartys' public nonce */
|
||||
void aggregate_pubnonces() {
|
||||
if (!sighash_loaded)
|
||||
throw InvalidArg("Session::aggregate_nonces sighash not loaded");
|
||||
|
||||
secp256k1_musig_pubnonce *pubnoncearr[signers.size()];
|
||||
|
||||
size_t i = 0;
|
||||
for (auto it = signers.begin(); it != signers.end(); ++it, ++i) {
|
||||
if (!it->pubnonce_loaded) //TODO pubkey of the offender in the error msg?
|
||||
throw InvalidArg("Session::aggregate_pubnonces missing or invalid pubnonce(s)");
|
||||
|
||||
pubnoncearr[i] = reinterpret_cast<secp256k1_musig_pubnonce*>(it->pubnonce);
|
||||
}
|
||||
|
||||
auto res = secp256k1_musig_nonce_agg
|
||||
( context.get()
|
||||
, &aggnonce
|
||||
, pubnoncearr
|
||||
, signers.size() );
|
||||
|
||||
if (!res)
|
||||
throw InvalidArg("Session::Impl::Impl secp256k1_musig_nonce_agg");
|
||||
|
||||
/* construct session */
|
||||
res = secp256k1_musig_nonce_process
|
||||
( context.get()
|
||||
, &session
|
||||
, &aggnonce
|
||||
, sighash
|
||||
, reinterpret_cast<const secp256k1_musig_keyagg_cache*>(&aggkey_cache)
|
||||
);
|
||||
|
||||
if (!res)
|
||||
throw InvalidArg("Session::Impl::Impl secp256k1_musig_nonce_process");
|
||||
}
|
||||
|
||||
void load_partial_at( std::string const& part_sig
|
||||
, Signatory& signer ) {
|
||||
auto buf = Util::Str::hexread(part_sig);
|
||||
if (buf.size() != 32) /* <-- size of a serialized partial sig. */
|
||||
throw InvalidArg("bad size PartialSig");
|
||||
|
||||
auto res = secp256k1_musig_partial_sig_parse
|
||||
( context.get()
|
||||
, reinterpret_cast<secp256k1_musig_partial_sig*>(&(signer.part_sig))
|
||||
, reinterpret_cast<const unsigned char*>(&buf[0])
|
||||
);
|
||||
if (res == 0)
|
||||
throw InvalidArg("secp256k1_musig_partial_sig_parse");
|
||||
|
||||
signer.partial_loaded = true;
|
||||
}
|
||||
|
||||
void verify_part_sigs() {
|
||||
auto res = 0;
|
||||
for (auto it = signers.begin(); it != signers.end(); ++it) {
|
||||
if (!it->partial_loaded) //TODO pubkey of the offender in the error msg?
|
||||
throw InvalidArg("Session::verify_part_sigs missing or invalid partial sig(s)");
|
||||
|
||||
res = secp256k1_musig_partial_sig_verify
|
||||
( context.get()
|
||||
, reinterpret_cast<const secp256k1_musig_partial_sig*>(&(it->part_sig))
|
||||
, reinterpret_cast<const secp256k1_musig_pubnonce*>(&(it->pubnonce))
|
||||
, reinterpret_cast<const secp256k1_pubkey*>(it->public_key.get_key())
|
||||
, reinterpret_cast<const secp256k1_musig_keyagg_cache*>(&aggkey_cache)
|
||||
, &session
|
||||
);
|
||||
|
||||
if (res == 0)
|
||||
throw InvalidArg("secp256k1_musig_partial_sig_verify");
|
||||
}
|
||||
}
|
||||
|
||||
void sign_partial() {
|
||||
secp256k1_keypair kp;
|
||||
auto res = secp256k1_keypair_create
|
||||
( context.get()
|
||||
, &kp
|
||||
, signing_key );
|
||||
|
||||
if (!res)
|
||||
throw InvalidArg("Session::sign_partial secp256k1_keypair_create");
|
||||
|
||||
res = secp256k1_musig_partial_sign
|
||||
( context.get()
|
||||
, reinterpret_cast<secp256k1_musig_partial_sig*>(&(local.part_sig))
|
||||
, &secnonce
|
||||
, reinterpret_cast<const secp256k1_keypair*>(&kp)
|
||||
, reinterpret_cast<const secp256k1_musig_keyagg_cache*>(&aggkey_cache)
|
||||
, &session
|
||||
);
|
||||
|
||||
if (!res)
|
||||
throw InvalidArg("Session::sign_partial secp256k1_musig_partial_sign");
|
||||
|
||||
local.partial_loaded = true;
|
||||
}
|
||||
|
||||
std::string serialize_partial() {
|
||||
std::uint8_t buf[32];
|
||||
auto res = secp256k1_musig_partial_sig_serialize
|
||||
( context.get()
|
||||
, reinterpret_cast<unsigned char*>(&buf[0])
|
||||
, reinterpret_cast<const secp256k1_musig_partial_sig*>(&(local.part_sig))
|
||||
);
|
||||
if (res == 0)
|
||||
throw InvalidArg("secp256k1_musig_partial_sig_serialize");
|
||||
|
||||
auto os = std::ostringstream();
|
||||
for (auto i{0}; i < 32; ++i)
|
||||
os << Util::Str::hexbyte(buf[i]);
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
void /* aggregate the partial sigs */
|
||||
aggregate_partials(std::uint8_t aggsigbuf[64]) {
|
||||
const secp256k1_musig_partial_sig *sig_array[signers.size()];
|
||||
|
||||
auto i = 0;
|
||||
for (auto it = signers.begin(); it != signers.end(); ++it, ++i)
|
||||
sig_array[i] = reinterpret_cast<const secp256k1_musig_partial_sig*>(&(it->part_sig));
|
||||
|
||||
auto res = secp256k1_musig_partial_sig_agg
|
||||
( context.get()
|
||||
, aggsigbuf
|
||||
, &session
|
||||
, reinterpret_cast<const secp256k1_musig_partial_sig* const*>(sig_array)
|
||||
, signers.size()
|
||||
);
|
||||
|
||||
if (!res)
|
||||
throw InvalidArg("Session::aggregate_partials secp256k1_partial_sig_agg");
|
||||
}
|
||||
|
||||
std::string dump_pubnonce(secp256k1_musig_pubnonce *p_nonce) {
|
||||
std::uint8_t buf[66];
|
||||
|
||||
auto res = secp256k1_musig_pubnonce_serialize
|
||||
( context.get()
|
||||
, buf
|
||||
, p_nonce
|
||||
);
|
||||
assert(res == 1);
|
||||
|
||||
auto os = std::ostringstream();
|
||||
for (auto i{0}; i < 66; ++i)
|
||||
os << Util::Str::hexbyte(buf[i]);
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
Signatory& find_signer(PubKey const& pk) {
|
||||
auto it = signers.begin();
|
||||
for ( ; it != signers.end(); ++it)
|
||||
if (pk == it->public_key) break;
|
||||
|
||||
if (it == signers.end()) //TODO throw out of range?
|
||||
throw InvalidArg("Musig::Session::Impl find_signer");
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
Sha256::Hash get_sighash() {
|
||||
Sha256::Hash rv;
|
||||
rv.from_buffer(sighash);
|
||||
return rv;
|
||||
}
|
||||
|
||||
XonlyPubKey internal_key() {
|
||||
return aggregate_pk;
|
||||
}
|
||||
|
||||
XonlyPubKey const& get_output_key() {
|
||||
return output_key;
|
||||
}
|
||||
};
|
||||
|
||||
Session::Session( Secp256k1::KeyPair const& keys
|
||||
, std::vector<Secp256k1::PubKey> const& pkvec
|
||||
)
|
||||
{
|
||||
auto signatory = Signatory();
|
||||
auto signers = std::vector<Signatory>(pkvec.size());
|
||||
auto it_local = signers.end();
|
||||
|
||||
for (size_t i{0}; i < pkvec.size(); ++i) {
|
||||
signatory.public_key = pkvec[i];
|
||||
signers[i] = std::move(signatory);
|
||||
|
||||
if (keys.pub() == pkvec[i])
|
||||
it_local = signers.begin() +i;
|
||||
}
|
||||
|
||||
if (it_local == signers.end())
|
||||
throw InvalidArg("Musig::Session::Session local pubkey arg missing");
|
||||
|
||||
pimpl = std::make_unique<Impl>
|
||||
( std::move(signers)
|
||||
, *it_local
|
||||
, keys
|
||||
);
|
||||
}
|
||||
|
||||
/* useful only for class or namespace level Session objects that do not
|
||||
* have the data available to fully construct when they are instantiated. */
|
||||
Session::Session() : pimpl(nullptr) { }
|
||||
|
||||
Session::~Session() =default;
|
||||
Session::Session(Session&&) =default;
|
||||
Session& Session::operator= (Session&&) =default;
|
||||
|
||||
XonlyPubKey
|
||||
Session::pubkey_aggregate(std::vector<PubKey> const& signers) {
|
||||
const secp256k1_pubkey *pkarr[signers.size()];
|
||||
|
||||
for (size_t i{0}; i < signers.size(); ++i)
|
||||
pkarr[i] = reinterpret_cast<const secp256k1_pubkey*>(signers[i].get_key());
|
||||
|
||||
return Impl::pubkey_aggregate(nullptr, pkarr, signers.size());
|
||||
}
|
||||
|
||||
void
|
||||
Session::apply_xonly_tweak(std::vector<std::uint8_t> const& scripthash) {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
std::uint8_t buf[32];
|
||||
pimpl->serialize_aggkey(buf);
|
||||
pimpl->xonly_tweak(buf, scripthash);
|
||||
}
|
||||
|
||||
void Session::load_sighash(Sha256::Hash const& sh) {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
return pimpl->load_sighash(sh);
|
||||
}
|
||||
|
||||
std::string
|
||||
Session::generate_local_nonces(Secp256k1::Random& random) {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
return pimpl->generate_local_nonces(random);
|
||||
}
|
||||
|
||||
void Session::load_pubnonce_at( std::string const& pn
|
||||
, PubKey const& pk ) {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
auto& signer = pimpl->find_signer(pk);
|
||||
pimpl->load_pubnonce_at(pn, signer);
|
||||
}
|
||||
|
||||
void Session::aggregate_pubnonces() {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
pimpl->aggregate_pubnonces();
|
||||
}
|
||||
|
||||
void Session::load_partial_at( std::string const& ps
|
||||
, PubKey const& pk ) {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
auto& signer = pimpl->find_signer(pk);
|
||||
pimpl->load_partial_at(ps, signer);
|
||||
}
|
||||
|
||||
void Session::verify_part_sigs() {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
pimpl->verify_part_sigs();
|
||||
}
|
||||
|
||||
void Session::sign_partial() {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
return pimpl->sign_partial();
|
||||
}
|
||||
|
||||
std::string Session::serialize_partial() {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
return pimpl->serialize_partial();
|
||||
}
|
||||
|
||||
void
|
||||
Session::aggregate_partials(std::uint8_t buffer[64]) {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
pimpl->aggregate_partials(buffer);
|
||||
}
|
||||
|
||||
Sha256::Hash Session::get_sighash() {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
return pimpl->get_sighash();
|
||||
}
|
||||
|
||||
XonlyPubKey Session::get_internal_key() {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
return pimpl->internal_key();
|
||||
}
|
||||
|
||||
XonlyPubKey const& Session::get_output_key() {
|
||||
if (!pimpl)
|
||||
throw InvalidArg("Musig::Session object uninitialized");
|
||||
|
||||
return pimpl->get_output_key();
|
||||
}
|
||||
|
||||
} }
|
||||
85
Secp256k1/Musig.hpp
Normal file
85
Secp256k1/Musig.hpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef SECP256K1_MUSIG_HPP
|
||||
#define SECP256K1_MUSIG_HPP
|
||||
|
||||
#include"Sha256/Hash.hpp"
|
||||
#include"Secp256k1/KeyPair.hpp"
|
||||
#include<memory>
|
||||
#include<vector>
|
||||
#include<stdexcept>
|
||||
#include<string>
|
||||
|
||||
namespace Secp256k1 { class PrivKey; }
|
||||
namespace Secp256k1 { class PubKey; }
|
||||
namespace Secp256k1 { class Random; }
|
||||
|
||||
namespace Secp256k1 { namespace Musig {
|
||||
|
||||
/* Thrown in case of being fed an invalid argument. */
|
||||
class InvalidArg : public std::invalid_argument {
|
||||
public:
|
||||
InvalidArg(std::string arg)
|
||||
: std::invalid_argument("Invalid argument: " +arg) { }
|
||||
};
|
||||
|
||||
/** musig dance
|
||||
* - order of the steps is partly flexible (e.g. steps 4-5 and 6-7 can
|
||||
* happen in any order)
|
||||
* - steps 1:a and 1:b are for bip341 script trees only
|
||||
*
|
||||
* 1) aggregate pubkeys of all participants
|
||||
* :a) compute script tree root hash (comprising bip340 tagged hashes of
|
||||
* all script leaves and branches)
|
||||
* :b) apply script tree root hash as tweak to internal key (which is
|
||||
* the aggregate key produced in step 1)
|
||||
* 2) compute sighash according to bip341
|
||||
* 3) create local sec+pub nonces
|
||||
* 4) send local pub nonce to all participants
|
||||
* 5) receive remote pubnonces, aggregate all into a single pubnonce
|
||||
* 6) sign local part sig
|
||||
* 7) receive partial sigs from all participants
|
||||
* 8) aggregate all partial sigs into a single schnorr sig
|
||||
*/
|
||||
|
||||
class Session {
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> pimpl;
|
||||
|
||||
protected:
|
||||
Session( Secp256k1::KeyPair const& signing_keys
|
||||
, std::vector<Secp256k1::PubKey> const& pkvec
|
||||
);
|
||||
|
||||
Session();
|
||||
Session(Session&&);
|
||||
Session& operator= (Session&&);
|
||||
|
||||
/* disallow copys to prevent the secnonce proliferating. */
|
||||
Session(Session const&) =delete;
|
||||
Session& operator=(Session const&) =delete;
|
||||
|
||||
void load_partial_at(std::string const&, PubKey const&);
|
||||
void load_pubnonce_at(std::string const&, PubKey const&);
|
||||
|
||||
public:
|
||||
virtual ~Session();
|
||||
|
||||
static
|
||||
XonlyPubKey pubkey_aggregate(std::vector<PubKey> const&);
|
||||
|
||||
void apply_xonly_tweak(std::vector<std::uint8_t> const& scripthash);
|
||||
void load_sighash(Sha256::Hash const&);
|
||||
std::string generate_local_nonces(Secp256k1::Random&);
|
||||
void aggregate_pubnonces();
|
||||
void verify_part_sigs();
|
||||
void sign_partial();
|
||||
std::string serialize_partial();
|
||||
void aggregate_partials(std::uint8_t aggsigbuf[64]);
|
||||
Sha256::Hash get_sighash();
|
||||
XonlyPubKey get_internal_key();
|
||||
XonlyPubKey const& get_output_key();
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
#endif /* SECP256K1_MUSIG_HPP */
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include<basicsecure.h>
|
||||
#include<iomanip>
|
||||
#include<secp256k1.h>
|
||||
#include<secp256k1_extrakeys.h>
|
||||
#include<sstream>
|
||||
#include<string>
|
||||
#include<string.h>
|
||||
|
|
@ -90,6 +91,31 @@ public:
|
|||
/* swap. */
|
||||
key = tmp;
|
||||
}
|
||||
void tweakadd(const unsigned char tweak[32]) {
|
||||
auto res = secp256k1_ec_pubkey_tweak_add( context.get()
|
||||
, &key
|
||||
, tweak
|
||||
);
|
||||
/* FIXME: use a backtrace-prserving exception. */
|
||||
if (!res)
|
||||
throw std::out_of_range(
|
||||
"Secp256k1::PubKey::operator+=: "
|
||||
"result of tweak-adding PubKey out-of-range"
|
||||
);
|
||||
}
|
||||
|
||||
void xonly_tweak( const unsigned char x_key[32]
|
||||
, const unsigned char tweak[32]
|
||||
) {
|
||||
int ret = secp256k1_xonly_pubkey_tweak_add
|
||||
( context.get()
|
||||
, &key
|
||||
, reinterpret_cast<const secp256k1_xonly_pubkey*>(x_key)
|
||||
, tweak
|
||||
);
|
||||
if (!ret)
|
||||
throw InvalidPubKey();
|
||||
}
|
||||
|
||||
bool equal(Impl const& o) const {
|
||||
std::uint8_t a[33];
|
||||
|
|
@ -149,8 +175,9 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/* Get G (with prepended tie-breaker byte). */
|
||||
PubKey::PubKey()
|
||||
: pimpl(Util::make_unique<Impl>(*G.pimpl)) { }
|
||||
: pimpl(Util::make_unique<Impl>(*G_tied.pimpl)) { }
|
||||
|
||||
PubKey::PubKey(secp256k1_context_struct *ctx, std::uint8_t buffer[33])
|
||||
: pimpl(Util::make_unique<Impl>(ctx, buffer)) {}
|
||||
|
|
@ -194,6 +221,7 @@ PubKey& PubKey::operator+=(PubKey const& o) {
|
|||
pimpl->add(*o.pimpl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubKey& PubKey::operator*=(PrivKey const& o) {
|
||||
pimpl->mul(o);
|
||||
return *this;
|
||||
|
|
@ -203,6 +231,14 @@ bool PubKey::operator==(PubKey const& o) const {
|
|||
return pimpl->equal(*o.pimpl);
|
||||
}
|
||||
|
||||
PubKey PubKey::xonly_tweak( XonlyPubKey const& x_key
|
||||
, const unsigned char tweak[32] ) {
|
||||
auto rv = PubKey();
|
||||
rv.pimpl->xonly_tweak( reinterpret_cast<const unsigned char*>(x_key.get_key())
|
||||
, tweak );
|
||||
return rv;
|
||||
}
|
||||
|
||||
void PubKey::to_buffer(std::uint8_t buffer[33]) const {
|
||||
pimpl->to_buffer(buffer);
|
||||
}
|
||||
|
|
@ -213,3 +249,115 @@ std::ostream& operator<<(std::ostream& os, Secp256k1::PubKey const& pk) {
|
|||
pk.pimpl->dump(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
namespace Secp256k1 {
|
||||
|
||||
class XonlyPubKey::Impl {
|
||||
public:
|
||||
secp256k1_xonly_pubkey key;
|
||||
|
||||
Impl(std::uint8_t const buffer[32]) {
|
||||
auto res = secp256k1_xonly_pubkey_parse( context.get()
|
||||
, &key
|
||||
, buffer
|
||||
);
|
||||
if (!res)
|
||||
throw InvalidPubKey();
|
||||
}
|
||||
Impl(secp256k1_context_struct *ctx, std::uint8_t buffer[32]) {
|
||||
auto res = secp256k1_xonly_pubkey_parse( ctx
|
||||
, &key
|
||||
, buffer
|
||||
);
|
||||
if (!res)
|
||||
throw InvalidPubKey();
|
||||
}
|
||||
|
||||
Impl() { }
|
||||
|
||||
bool equal(Impl const& o) const {
|
||||
std::uint8_t a[32];
|
||||
std::uint8_t b[32];
|
||||
|
||||
auto resa = secp256k1_xonly_pubkey_serialize( context.get()
|
||||
, a
|
||||
, &key
|
||||
);
|
||||
assert(resa == 1);
|
||||
|
||||
auto resb = secp256k1_xonly_pubkey_serialize( context.get()
|
||||
, b
|
||||
, &o.key
|
||||
);
|
||||
assert(resb == 1);
|
||||
|
||||
return basicsecure_eq(a, b, sizeof(a));
|
||||
}
|
||||
};
|
||||
|
||||
/* Get G (the 32 byte x coordinate). */
|
||||
XonlyPubKey::XonlyPubKey()
|
||||
: pimpl(Util::make_unique<Impl>(*G_xcoord.pimpl)) { }
|
||||
|
||||
XonlyPubKey::XonlyPubKey(std::string const& s) {
|
||||
auto buf = Util::Str::hexread(s);
|
||||
if (buf.size() != 32)
|
||||
throw InvalidPubKey();
|
||||
pimpl = Util::make_unique<Impl>(&buf[0]);
|
||||
}
|
||||
XonlyPubKey::XonlyPubKey( secp256k1_context_struct *ctx
|
||||
, std::uint8_t buffer[32])
|
||||
: pimpl(Util::make_unique<Impl>(ctx, buffer)) {}
|
||||
XonlyPubKey::XonlyPubKey(std::uint8_t const buffer[32])
|
||||
: pimpl(Util::make_unique<Impl>(buffer)) {}
|
||||
|
||||
|
||||
XonlyPubKey::XonlyPubKey(XonlyPubKey const& o)
|
||||
: pimpl(Util::make_unique<Impl>(*o.pimpl)) { }
|
||||
|
||||
XonlyPubKey::XonlyPubKey(XonlyPubKey&& o) {
|
||||
auto mine = Util::make_unique<Impl>();
|
||||
std::swap(pimpl, mine);
|
||||
std::swap(pimpl, o.pimpl);
|
||||
}
|
||||
|
||||
XonlyPubKey::~XonlyPubKey() { }
|
||||
|
||||
void const* XonlyPubKey::get_key() const {
|
||||
return &pimpl->key;
|
||||
}
|
||||
|
||||
bool XonlyPubKey::operator==(XonlyPubKey const& o) const {
|
||||
return pimpl->equal(*o.pimpl);
|
||||
}
|
||||
|
||||
void
|
||||
XonlyPubKey::to_buffer(std::uint8_t buffer[32]) const {
|
||||
auto res = secp256k1_xonly_pubkey_serialize
|
||||
( context.get()
|
||||
, reinterpret_cast<unsigned char*>(buffer)
|
||||
, &(pimpl->key) );
|
||||
assert(res == 1);
|
||||
}
|
||||
|
||||
XonlyPubKey
|
||||
XonlyPubKey::from_ecdsa_pk(PubKey const& ecpk) {
|
||||
return from_ecdsa_pk(reinterpret_cast<std::uint8_t const*>(ecpk.get_key()));
|
||||
}
|
||||
|
||||
XonlyPubKey
|
||||
XonlyPubKey::from_ecdsa_pk(std::uint8_t const ecpk_buf[33]) {
|
||||
auto rv = XonlyPubKey();
|
||||
int parity;
|
||||
auto res = secp256k1_xonly_pubkey_from_pubkey
|
||||
( context.get()
|
||||
, (secp256k1_xonly_pubkey*) &(rv.pimpl->key)
|
||||
, &parity
|
||||
, (const secp256k1_pubkey*) ecpk_buf
|
||||
);
|
||||
assert(res == 1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include<stdexcept>
|
||||
#include<string>
|
||||
#include<utility>
|
||||
#include<vector>
|
||||
|
||||
extern "C" {
|
||||
struct secp256k1_context_struct;
|
||||
|
|
@ -17,7 +18,10 @@ struct secp256k1_context_struct;
|
|||
|
||||
namespace Secp256k1 { class PrivKey; }
|
||||
namespace Secp256k1 { class PubKey; }
|
||||
namespace Secp256k1 { class XonlyPubKey; }
|
||||
namespace Secp256k1 { class Signature; }
|
||||
namespace Secp256k1 { class SchnorrSig; }
|
||||
namespace Secp256k1 { namespace Musig { class Session; } }
|
||||
|
||||
std::ostream& operator<<(std::ostream&, Secp256k1::PubKey const&);
|
||||
|
||||
|
|
@ -41,7 +45,7 @@ private:
|
|||
void const* get_key() const;
|
||||
|
||||
public:
|
||||
/* Get G. */
|
||||
/* Get G (with prepended tie-breaker byte). */
|
||||
PubKey();
|
||||
/* Load public key from a hex-encoded string. */
|
||||
explicit PubKey(std::string const&);
|
||||
|
|
@ -105,6 +109,9 @@ public:
|
|||
|
||||
friend std::ostream& ::operator<<(std::ostream&, PubKey const&);
|
||||
|
||||
static PubKey xonly_tweak( XonlyPubKey const&
|
||||
, const unsigned char tweak[32] );
|
||||
|
||||
static PubKey from_buffer(std::uint8_t const buffer[33]) {
|
||||
return PubKey(buffer);
|
||||
}
|
||||
|
|
@ -118,6 +125,8 @@ public:
|
|||
void to_buffer(std::uint8_t buffer[33]) const;
|
||||
|
||||
friend class Secp256k1::Signature;
|
||||
friend class Secp256k1::XonlyPubKey;
|
||||
friend class Secp256k1::Musig::Session;
|
||||
};
|
||||
|
||||
inline
|
||||
|
|
@ -125,6 +134,67 @@ PubKey operator*(PrivKey const& a, PubKey const& B) {
|
|||
return B * a;
|
||||
}
|
||||
|
||||
// TODO class template?
|
||||
class XonlyPubKey {
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> pimpl;
|
||||
|
||||
explicit XonlyPubKey( secp256k1_context_struct *
|
||||
, std::uint8_t buffer[32]);
|
||||
explicit XonlyPubKey(std::uint8_t const buffer[32]);
|
||||
|
||||
/* Used by SchnorrSig::valid. */
|
||||
void const* get_key() const;
|
||||
|
||||
public:
|
||||
/* Get G (the 32 byte x coordinate). */
|
||||
XonlyPubKey();
|
||||
/* Load public key from a hex-encoded string. */
|
||||
explicit XonlyPubKey(std::string const&);
|
||||
|
||||
/* Copy an existing public key. */
|
||||
XonlyPubKey(XonlyPubKey const&);
|
||||
XonlyPubKey(XonlyPubKey&&);
|
||||
|
||||
~XonlyPubKey();
|
||||
|
||||
XonlyPubKey& operator=(XonlyPubKey const& o) {
|
||||
auto tmp = XonlyPubKey(o);
|
||||
tmp.pimpl.swap(pimpl);
|
||||
return *this;
|
||||
}
|
||||
XonlyPubKey& operator=(XonlyPubKey&& o) {
|
||||
auto tmp = XonlyPubKey(std::move(o));
|
||||
tmp.pimpl.swap(pimpl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(XonlyPubKey const&) const;
|
||||
bool operator!=(XonlyPubKey const& o) const {
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
void to_buffer(std::uint8_t buffer[32]) const;
|
||||
|
||||
/* find the x-only equivalent key from a 33 byte ecdsa key. */
|
||||
static XonlyPubKey from_ecdsa_pk(PubKey const&);
|
||||
static XonlyPubKey from_ecdsa_pk(std::uint8_t const buffer[33]);
|
||||
|
||||
static XonlyPubKey from_buffer(std::uint8_t const buffer[32]) {
|
||||
return XonlyPubKey(buffer);
|
||||
}
|
||||
/* Needed for the generator point. */
|
||||
static XonlyPubKey from_buffer_with_context( secp256k1_context_struct *ctx
|
||||
, std::uint8_t buffer[32]
|
||||
) {
|
||||
return XonlyPubKey(ctx, buffer);
|
||||
}
|
||||
|
||||
friend class Secp256k1::PubKey;
|
||||
friend class Secp256k1::SchnorrSig;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_PUBKEY_HPP */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include<assert.h>
|
||||
#include<secp256k1.h>
|
||||
#include<secp256k1_schnorrsig.h>
|
||||
#include<string.h>
|
||||
#include"Secp256k1/Detail/context.hpp"
|
||||
#include"Secp256k1/PrivKey.hpp"
|
||||
|
|
@ -166,4 +167,67 @@ Signature::der_decode(std::vector<std::uint8_t> const& d) {
|
|||
return rv;
|
||||
}
|
||||
|
||||
SchnorrSig::SchnorrSig(std::uint8_t const buffer[64]) {
|
||||
memcpy(&data[0], &buffer[0], 64);
|
||||
}
|
||||
|
||||
SchnorrSig::SchnorrSig( Secp256k1::PrivKey const& sk
|
||||
, Sha256::Hash const& m
|
||||
) {
|
||||
std::uint8_t mbuf[32];
|
||||
m.to_buffer(mbuf);
|
||||
std::uint8_t skbuf[32];
|
||||
sk.to_buffer(skbuf);
|
||||
|
||||
secp256k1_keypair kp;
|
||||
auto res = secp256k1_keypair_create
|
||||
( context.get()
|
||||
, &kp
|
||||
, skbuf );
|
||||
|
||||
if (!res)
|
||||
throw InvalidPrivKey();
|
||||
|
||||
res = secp256k1_schnorrsig_sign32
|
||||
( context.get()
|
||||
, data
|
||||
, mbuf
|
||||
, &kp
|
||||
, nullptr ); //FIXME 32 random bytes
|
||||
if (!res)
|
||||
throw InvalidPrivKey();
|
||||
}
|
||||
|
||||
SchnorrSig::SchnorrSig() {
|
||||
memset(data, 0, 64);
|
||||
}
|
||||
|
||||
bool SchnorrSig::valid( Secp256k1::XonlyPubKey const& pk
|
||||
, Sha256::Hash const& m
|
||||
) const {
|
||||
std::uint8_t mbuf[32];
|
||||
m.to_buffer(mbuf);
|
||||
size_t msglen = sizeof(mbuf);
|
||||
|
||||
auto res = secp256k1_schnorrsig_verify
|
||||
( context.get()
|
||||
, reinterpret_cast<const unsigned char*>(data)
|
||||
, reinterpret_cast<const unsigned char*>(mbuf)
|
||||
, msglen
|
||||
, reinterpret_cast<const secp256k1_xonly_pubkey*>(pk.get_key())
|
||||
);
|
||||
return res != 0;
|
||||
}
|
||||
|
||||
void SchnorrSig::to_buffer(std::uint8_t buffer[64]) const {
|
||||
memcpy(&buffer[0], &data[0], 64);
|
||||
}
|
||||
std::vector<std::uint8_t> SchnorrSig::to_buffer() const {
|
||||
auto buf = std::vector<std::uint8_t>(64);
|
||||
for (auto i{0}; i < 64; ++i)
|
||||
buf[i] = data[i];
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
namespace Secp256k1 { class PrivKey; }
|
||||
namespace Secp256k1 { class PubKey; }
|
||||
namespace Secp256k1 { class XonlyPubKey; }
|
||||
namespace Sha256 { class Hash; }
|
||||
|
||||
namespace Secp256k1 {
|
||||
|
|
@ -78,6 +79,47 @@ public:
|
|||
Signature der_decode(std::vector<std::uint8_t> const& d);
|
||||
};
|
||||
|
||||
class SchnorrSig {
|
||||
private:
|
||||
std::uint8_t data[64];
|
||||
|
||||
SchnorrSig( Secp256k1::PrivKey const&
|
||||
, Sha256::Hash const&
|
||||
);
|
||||
SchnorrSig(std::uint8_t const buffer[64]);
|
||||
|
||||
public:
|
||||
SchnorrSig();
|
||||
SchnorrSig(SchnorrSig const&) =default;
|
||||
SchnorrSig& operator=(SchnorrSig const&) =default;
|
||||
|
||||
static
|
||||
SchnorrSig from_buffer(std::uint8_t buffer[64]) {
|
||||
return SchnorrSig(buffer);
|
||||
}
|
||||
|
||||
void to_buffer(std::uint8_t buffer[64]) const;
|
||||
std::vector<std::uint8_t> to_buffer() const;
|
||||
|
||||
/* Check if the signature is valid for the given pubkey
|
||||
* and message hash.
|
||||
* We impose the low-s rule. // TODO investigate
|
||||
*/
|
||||
bool valid( Secp256k1::XonlyPubKey const& pk
|
||||
, Sha256::Hash const& m
|
||||
) const;
|
||||
|
||||
/* Create a valid signature for the given privkey and
|
||||
* message hash.
|
||||
*/
|
||||
static
|
||||
SchnorrSig create( Secp256k1::PrivKey const& sk
|
||||
, Sha256::Hash const& m
|
||||
) {
|
||||
return SchnorrSig(sk, m);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_SIGNATURE_HPP */
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
#ifndef SECP256K1_SIGNERIF_HPP
|
||||
#define SECP256K1_SIGNERIF_HPP
|
||||
|
||||
#include<vector>
|
||||
#include<cstdint>
|
||||
|
||||
namespace Secp256k1 { class PrivKey; }
|
||||
namespace Secp256k1 { class PubKey; }
|
||||
namespace Secp256k1 { class KeyPair; }
|
||||
namespace Secp256k1 { class Signature; }
|
||||
namespace Sha256 { class Hash; }
|
||||
|
||||
|
|
@ -51,6 +53,12 @@ public:
|
|||
Sha256::Hash
|
||||
get_privkey_salted_hash( std::uint8_t salt[32]
|
||||
) =0;
|
||||
|
||||
/* Get the keypair corresponding to a given tweak. */
|
||||
virtual
|
||||
Secp256k1::KeyPair
|
||||
get_keypair_tweak(Secp256k1::PrivKey const& tweak) =0;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
76
Secp256k1/TapscriptTree.cpp
Normal file
76
Secp256k1/TapscriptTree.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#include"Secp256k1/TapscriptTree.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Secp256k1/tagged_hashes.hpp"
|
||||
#include"Bitcoin/varint.hpp"
|
||||
#include<algorithm>
|
||||
#include<sstream>
|
||||
#include<string>
|
||||
#include<utility>
|
||||
#include<assert.h>
|
||||
#include<string.h>
|
||||
|
||||
namespace Secp256k1 { namespace TapTree {
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
encoded_leaf(std::vector<std::uint8_t> const& script) {
|
||||
auto sz = script.size();
|
||||
// TODO minimum size 2 for a witness script?
|
||||
|
||||
auto ss = std::ostringstream();
|
||||
ss << Bitcoin::varint(sz);
|
||||
auto varintstr = ss.str();
|
||||
size_t bytes = varintstr.size();
|
||||
|
||||
if (bytes != 1 && bytes != 3 && bytes != 5 && bytes != 9)
|
||||
throw InvalidArg("bip341 script leaf incorrect CompactSize encoding");
|
||||
|
||||
auto leafbuf = std::vector<std::uint8_t>();
|
||||
leafbuf.resize( 1 /* version */
|
||||
+ bytes /* varint byte(s) */
|
||||
+ sz );
|
||||
|
||||
leafbuf[0] = 0xc0; /* script leaf version 0xc0 for bip341 script paths */
|
||||
memcpy(&leafbuf[1], varintstr.data(), bytes);
|
||||
memcpy(&leafbuf[bytes+1], script.data(), sz);
|
||||
|
||||
return leafbuf;
|
||||
}
|
||||
|
||||
LeafPair::LeafPair( std::vector<std::uint8_t> const& script0
|
||||
, std::vector<std::uint8_t> const& script1 )
|
||||
: a(std::vector<std::uint8_t>(32))
|
||||
, b(std::vector<std::uint8_t>(32))
|
||||
{
|
||||
/* tagged hash of each leaf */
|
||||
Secp256k1::tagged_hash( &a[0]
|
||||
, reinterpret_cast<const uint8_t*>(script0.data())
|
||||
, script0.size()
|
||||
, Tag::LEAF );
|
||||
Secp256k1::tagged_hash( &b[0]
|
||||
, reinterpret_cast<const uint8_t*>(script1.data())
|
||||
, script1.size()
|
||||
, Tag::LEAF );
|
||||
|
||||
if (!std::lexicographical_compare( a.begin(), a.end(),
|
||||
b.begin(), b.end()) )
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
LeafPair::compute_branch_hash() {
|
||||
/* tagged hash of the (only) branch.
|
||||
* it is the merkle root of this script tree. */
|
||||
unsigned char leaf_hashes[64];
|
||||
memcpy(&leaf_hashes[0], a.data(), a.size());
|
||||
memcpy(&leaf_hashes[32], b.data(), b.size());
|
||||
|
||||
auto buffer = std::vector<std::uint8_t>(32);
|
||||
Secp256k1::tagged_hash( &buffer[0]
|
||||
, leaf_hashes
|
||||
, 64
|
||||
, Tag::BRANCH );
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
38
Secp256k1/TapscriptTree.hpp
Normal file
38
Secp256k1/TapscriptTree.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef SECP256K1_TAPTREE_HPP
|
||||
#define SECP256K1_TAPTREE_HPP
|
||||
|
||||
#include<stdexcept>
|
||||
#include<vector>
|
||||
|
||||
namespace Secp256k1 { class XonlyPubKey; }
|
||||
|
||||
namespace Secp256k1 { namespace TapTree {
|
||||
|
||||
/* Thrown in case of being fed an invalid argument. */
|
||||
class InvalidArg : public std::invalid_argument {
|
||||
public:
|
||||
InvalidArg(std::string arg)
|
||||
: std::invalid_argument("Invalid argument: " +arg) { }
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
encoded_leaf(std::vector<std::uint8_t> const&);
|
||||
|
||||
struct LeafPair {
|
||||
std::vector<std::uint8_t> a;
|
||||
std::vector<std::uint8_t> b;
|
||||
|
||||
LeafPair() =delete;
|
||||
LeafPair( std::vector<std::uint8_t> const&
|
||||
, std::vector<std::uint8_t> const& );
|
||||
|
||||
// FIXME RVO doesn't call move ctor?
|
||||
LeafPair& operator= (LeafPair&&) =default;
|
||||
LeafPair(LeafPair&&) =default;
|
||||
|
||||
std::vector<std::uint8_t> compute_branch_hash();
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif /* SECP256K1_TAPTREE_HPP */
|
||||
41
Secp256k1/tagged_hashes.cpp
Normal file
41
Secp256k1/tagged_hashes.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#include"Secp256k1/tagged_hashes.hpp"
|
||||
#include"Sha256/Hash.hpp"
|
||||
#include"Sha256/HasherStream.hpp"
|
||||
|
||||
namespace Tag {
|
||||
|
||||
namespace {
|
||||
|
||||
const char *taptagstrings[4] = {
|
||||
"TapLeaf",
|
||||
"TapBranch",
|
||||
"TapTweak",
|
||||
"TapSighash"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const char* str(Tag::tap tag) {
|
||||
return taptagstrings[static_cast<uint8_t>(tag)];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Secp256k1 {
|
||||
|
||||
void tagged_hash( std::uint8_t output[32]
|
||||
, std::uint8_t const input[]
|
||||
, size_t inputsize
|
||||
, Tag::tap tag ) {
|
||||
if (static_cast<uint8_t>(tag) > 3)
|
||||
return; // TODO throw exception
|
||||
|
||||
auto hasher = Sha256::HasherStream(tag);
|
||||
for (size_t i{0}; i < inputsize; i++)
|
||||
hasher.put(input[i]);
|
||||
|
||||
auto taggedhash = std::move(hasher).finalize();
|
||||
taggedhash.to_buffer(&output[0]);
|
||||
}
|
||||
|
||||
}
|
||||
22
Secp256k1/tagged_hashes.hpp
Normal file
22
Secp256k1/tagged_hashes.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef SECP256K1_TAGGED_HASHES_HPP
|
||||
#define SECP256K1_TAGGED_HASHES_HPP
|
||||
|
||||
#include<string>
|
||||
|
||||
namespace Tag {
|
||||
|
||||
enum tap : uint8_t { LEAF, BRANCH, TWEAK, SIGHASH };
|
||||
const char* str(tap);
|
||||
|
||||
}
|
||||
|
||||
namespace Secp256k1 {
|
||||
|
||||
void tagged_hash( std::uint8_t output[32]
|
||||
, std::uint8_t const input[]
|
||||
, size_t size
|
||||
, Tag::tap );
|
||||
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_TAGGED_HASHES_HPP */
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
#include"Sha256/Hash.hpp"
|
||||
#include"Sha256/Hasher.hpp"
|
||||
#include"Sha256/HasherStream.hpp"
|
||||
#include"Secp256k1/tagged_hashes.hpp"
|
||||
#include"Util/make_unique.hpp"
|
||||
#include<string.h>
|
||||
|
||||
namespace Sha256 { namespace Detail {
|
||||
|
||||
|
|
@ -45,6 +47,21 @@ Hash HasherStreamBuf::get_hash() const {
|
|||
/* Finalize the temporary. */
|
||||
return std::move(tmp).finalize();
|
||||
}
|
||||
void HasherStreamBuf::tag(Tag::tap tag) {
|
||||
/* Load buffer with bip340 tagged hashes. */
|
||||
if ((uint8_t) tag > 3)
|
||||
return; // TODO throw exception
|
||||
|
||||
auto *ptagtext = Tag::str(tag);
|
||||
auto tmp = Hasher();
|
||||
tmp.feed(ptagtext, strlen(ptagtext));
|
||||
auto hashedtag = std::move(tmp).finalize();
|
||||
uint8_t buffer[32];
|
||||
hashedtag.to_buffer(buffer);
|
||||
pimpl->hasher.feed(buffer, 32);
|
||||
pimpl->hasher.feed(buffer, 32);
|
||||
|
||||
}
|
||||
|
||||
HasherStreamBase::HasherStreamBase()
|
||||
: buf(Util::make_unique<HasherStreamBuf>()) { }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include<memory>
|
||||
|
||||
namespace Sha256 { class Hash; }
|
||||
namespace Tag { enum tap : uint8_t; }
|
||||
|
||||
namespace Sha256 {
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ public:
|
|||
|
||||
Hash finalize()&&;
|
||||
Hash get_hash() const;
|
||||
void tag(Tag::tap);
|
||||
};
|
||||
|
||||
/* Base class ensures buffer is constructed before ostream is. */
|
||||
|
|
@ -60,6 +62,10 @@ public:
|
|||
HasherStream() : std::ostream(buf.get()) { }
|
||||
/* HasherStream(HasherStream&&) =default; */
|
||||
|
||||
/* initialized with a bip340 tagged hash. */
|
||||
HasherStream(Tag::tap tag)
|
||||
: std::ostream(buf.get()) { buf->tag(tag); }
|
||||
|
||||
Hash finalize()&& {
|
||||
return std::move(*buf).finalize();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include"Jsmn/Object.hpp"
|
||||
#include"Json/Out.hpp"
|
||||
#include"Ln/Amount.hpp"
|
||||
#include"Secp256k1/KeyPair.hpp"
|
||||
#include"Secp256k1/PrivKey.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Secp256k1/Signature.hpp"
|
||||
|
|
@ -118,6 +119,11 @@ public:
|
|||
basicsecure_clear(buf, sizeof(buf));
|
||||
return std::move(hasher).finalize();
|
||||
}
|
||||
Secp256k1::KeyPair
|
||||
get_keypair_tweak(Secp256k1::PrivKey const& tweak
|
||||
) override {
|
||||
return Secp256k1::KeyPair(tweak * sk);
|
||||
}
|
||||
};
|
||||
|
||||
std::uint64_t to_number(std::string const& s) {
|
||||
|
|
|
|||
|
|
@ -8,37 +8,54 @@
|
|||
int main() {
|
||||
auto res = bool();
|
||||
auto hash = Ripemd160::Hash();
|
||||
auto pubkey_hash = Secp256k1::PubKey();
|
||||
auto locktime = std::uint32_t();
|
||||
auto pubkey_locktime = Secp256k1::PubKey();
|
||||
auto claim_pubkey_hash = Secp256k1::XonlyPubKey();
|
||||
|
||||
auto test = [&](std::string const& s) {
|
||||
res = Boltz::Detail::match_lockscript
|
||||
( hash, pubkey_hash, locktime, pubkey_locktime
|
||||
auto testclaim = [&](std::string const& s) {
|
||||
res = Boltz::Detail::match_claimscript
|
||||
( hash, claim_pubkey_hash
|
||||
, Util::Str::hexread(s)
|
||||
);
|
||||
};
|
||||
|
||||
test("8201208763a9142c2d5441ef4a4469eae063941463e3c65ee926c088210207262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750395e809b17521030a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac");
|
||||
auto refund_pubkey_hash = Secp256k1::XonlyPubKey();
|
||||
auto locktime = std::uint32_t();
|
||||
|
||||
auto testrefund = [&](std::string const& s) {
|
||||
res = Boltz::Detail::match_refundscript
|
||||
( locktime, refund_pubkey_hash
|
||||
, Util::Str::hexread(s)
|
||||
);
|
||||
};
|
||||
|
||||
testclaim("82012088a9142c2d5441ef4a4469eae063941463e3c65ee926c0882007262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd779ac");
|
||||
assert(res);
|
||||
assert(hash == Ripemd160::Hash("2c2d5441ef4a4469eae063941463e3c65ee926c0"));
|
||||
assert(pubkey_hash == Secp256k1::PubKey("0207262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd779"));
|
||||
assert(claim_pubkey_hash == Secp256k1::XonlyPubKey("07262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd779"));
|
||||
testrefund("200a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529ead0395e809b1");
|
||||
assert(res);
|
||||
assert(locktime == 649365);
|
||||
assert(pubkey_locktime == Secp256k1::PubKey("030a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e"));
|
||||
assert(refund_pubkey_hash == Secp256k1::XonlyPubKey("0a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e"));
|
||||
|
||||
test("8201218763a9142c2d5441ef4a4469eae063941463e3c65ee926c088210207262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750395e809b17521030a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac");
|
||||
testclaim("82012188a9142c2d5441ef4a4469eae063941463e3c65ee926c0882007262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd779ac");
|
||||
assert(!res);
|
||||
testrefund("210a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529ead0395e809b1");
|
||||
assert(!res);
|
||||
|
||||
test("8201208763a9142c2d5441ef4a4469eae063941463e3c65ee926c08821ff07262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750395e809b17521030a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac");
|
||||
// FIXME this test seems to test the tie-breaker byte at the start of the public key, meaningless with x-only keys...
|
||||
/*test("8201208763a9142c2d5441ef4a4469eae063941463e3c65ee926c08821ff07262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750395e809b17521030a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac");
|
||||
assert(!res);*/
|
||||
|
||||
testrefund("200a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529ead0295e8b1");
|
||||
assert(!res);
|
||||
|
||||
test("8201208763a9142c2d5441ef4a4469eae063941463e3c65ee926c088210207262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750295e8b17521030a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac");
|
||||
assert(!res);
|
||||
// FIXME this test seems to test the tie-breaker byte at the start of the public key, meaningless with x-only keys...
|
||||
/*test("8201208763a9142c2d5441ef4a4469eae063941463e3c65ee926c088210207262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750395e809b17521ff0a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac");
|
||||
assert(!res);*/
|
||||
|
||||
test("8201208763a9142c2d5441ef4a4469eae063941463e3c65ee926c088210207262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750395e809b17521ff0a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac");
|
||||
testrefund("200a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529ead0395e809b17551");
|
||||
assert(!res);
|
||||
testclaim("82012088a9142c2d5441ef4a4469eae063941463e3c65ee926c0882007262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd779ac7551");
|
||||
assert(!res);
|
||||
|
||||
test("8201208763a9142c2d5441ef4a4469eae063941463e3c65ee926c088210207262fc331c1c845c6f8f7cca7a04ec3bdb09ef82cddac3eda2953c10bddd77967750395e809b17521030a47fa92352ac70161366dad4b45ad2a2fcbf5cef40fec43b29d33128ace529e68ac7551");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include"Net/IPAddr.hpp"
|
||||
#include"Net/IPAddrOrOnion.hpp"
|
||||
#include"Net/IPBinnerBySubnet.hpp"
|
||||
#include"Secp256k1/KeyPair.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Secp256k1/Signature.hpp"
|
||||
#include"Secp256k1/SignerIF.hpp"
|
||||
|
|
@ -38,6 +39,11 @@ public:
|
|||
hash.from_buffer(salt);
|
||||
return hash;
|
||||
}
|
||||
Secp256k1::KeyPair
|
||||
get_keypair_tweak(Secp256k1::PrivKey const& tweak
|
||||
) override {
|
||||
throw std::logic_error("Not expected to use.");
|
||||
}
|
||||
};
|
||||
auto dummy_signer = DummySigner();
|
||||
|
||||
|
|
|
|||
180
tests/secp256k1/bip327.cpp
Normal file
180
tests/secp256k1/bip327.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#undef NDEBUG
|
||||
#include"Bitcoin/pubkey_to_scriptPubKey.hpp"
|
||||
#include"Secp256k1/TapscriptTree.hpp"
|
||||
#include"Secp256k1/Musig.hpp"
|
||||
#include"Secp256k1/PrivKey.hpp"
|
||||
#include"Secp256k1/PubKey.hpp"
|
||||
#include"Secp256k1/Random.hpp"
|
||||
#include"Secp256k1/Signature.hpp"
|
||||
#include"Secp256k1/SignerIF.hpp"
|
||||
#include"Sha256/fun.hpp"
|
||||
#include"Util/Str.hpp"
|
||||
#include"external/basicsecure/basicsecure.h"
|
||||
#include<assert.h>
|
||||
#include<iostream>
|
||||
#include<iterator>
|
||||
#include<sstream>
|
||||
#include<string>
|
||||
#include<tuple>
|
||||
#include<utility>
|
||||
#include<vector>
|
||||
|
||||
using namespace Secp256k1;
|
||||
|
||||
class Signer : public Secp256k1::SignerIF {
|
||||
private:
|
||||
Secp256k1::PrivKey sk;
|
||||
|
||||
public:
|
||||
/* Yeah, everyone totally knows the below private key, so insecure. */
|
||||
Signer() : sk("7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671") { }
|
||||
|
||||
Secp256k1::PubKey
|
||||
get_pubkey_tweak(Secp256k1::PrivKey const& tweak) override {
|
||||
return tweak * Secp256k1::PubKey(sk);
|
||||
}
|
||||
Secp256k1::Signature
|
||||
get_signature_tweak( Secp256k1::PrivKey const& tweak
|
||||
, Sha256::Hash const& m
|
||||
) override {
|
||||
throw std::logic_error("Not expected to use.");
|
||||
}
|
||||
Sha256::Hash
|
||||
get_privkey_salted_hash(std::uint8_t salt[32]) override {
|
||||
throw std::logic_error("Not expected to use.");
|
||||
}
|
||||
Secp256k1::KeyPair
|
||||
get_keypair_tweak(Secp256k1::PrivKey const& tweak
|
||||
) override {
|
||||
return Secp256k1::KeyPair(tweak * sk);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class BIP327MusigSession : public Musig::Session {
|
||||
public:
|
||||
BIP327MusigSession() : Secp256k1::Musig::Session() { }
|
||||
|
||||
BIP327MusigSession(BIP327MusigSession&&) =default;
|
||||
BIP327MusigSession& operator= (BIP327MusigSession&&) =default;
|
||||
|
||||
/* disallow copys to prevent the secnonce proliferating. */
|
||||
BIP327MusigSession(BIP327MusigSession const&) =delete;
|
||||
BIP327MusigSession& operator=(BIP327MusigSession const&) =delete;
|
||||
|
||||
BIP327MusigSession( Secp256k1::KeyPair const& pair
|
||||
, std::vector<Secp256k1::PubKey> const& pkvec
|
||||
)
|
||||
: Musig::Session(pair, pkvec)
|
||||
{ }
|
||||
|
||||
~BIP327MusigSession() =default;
|
||||
|
||||
void load_partial( std::string const& psig
|
||||
, Secp256k1::PubKey const& pk
|
||||
) {
|
||||
load_partial_at(psig, pk);
|
||||
}
|
||||
void load_pubnonce( std::string const& pnonce
|
||||
, PubKey const& pk
|
||||
) {
|
||||
load_pubnonce_at(pnonce, pk);
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<uint8_t> getroothash(std::string const& a, std::string const& b) {
|
||||
auto claimscript = Util::Str::hexread(a);
|
||||
auto refundscript = Util::Str::hexread(b);
|
||||
|
||||
/* leaves to buffers */
|
||||
auto leaf0 = Secp256k1::TapTree::encoded_leaf(claimscript);
|
||||
auto leaf1 = Secp256k1::TapTree::encoded_leaf(refundscript);
|
||||
|
||||
/* tagged hashes of leaves */
|
||||
auto leafhashes = Secp256k1::TapTree::LeafPair(leaf0, leaf1);
|
||||
|
||||
/* combine leaves into a branch */
|
||||
return leafhashes.compute_branch_hash();
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
auto rand = Random();
|
||||
auto signer = Signer();
|
||||
|
||||
auto tweak0 = PrivKey(rand);
|
||||
auto tweak1 = PrivKey(rand);
|
||||
auto tweak2 = PrivKey(rand);
|
||||
auto pk0 = signer.get_pubkey_tweak(tweak0);
|
||||
auto pk1 = signer.get_pubkey_tweak(tweak1);
|
||||
auto pk2 = signer.get_pubkey_tweak(tweak2);
|
||||
auto sighash = Sha256::Hash("F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF");
|
||||
const std::string leafA = "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac";
|
||||
const std::string leafB = "07546170726f6f74";
|
||||
|
||||
auto pkvec = std::vector<PubKey>{pk0, pk1, pk2};
|
||||
|
||||
auto kp0 = signer.get_keypair_tweak(tweak0);
|
||||
auto session0 = BIP327MusigSession(kp0, pkvec);
|
||||
session0.load_sighash(sighash);
|
||||
auto pn0 = session0.generate_local_nonces(rand);
|
||||
|
||||
auto kp1 = signer.get_keypair_tweak(tweak1);
|
||||
auto session1 = BIP327MusigSession(kp1, pkvec);
|
||||
session1.load_sighash(sighash);
|
||||
auto pn1 = session1.generate_local_nonces(rand);
|
||||
|
||||
auto kp2 = signer.get_keypair_tweak(tweak2);
|
||||
auto session2 = BIP327MusigSession(kp2, pkvec);
|
||||
session2.load_sighash(sighash);
|
||||
auto pn2 = session2.generate_local_nonces(rand);
|
||||
|
||||
/* combine leaves into a branch */
|
||||
auto root_hash = getroothash(leafA, leafB);
|
||||
session0.apply_xonly_tweak(root_hash);
|
||||
session1.apply_xonly_tweak(root_hash);
|
||||
session2.apply_xonly_tweak(root_hash);
|
||||
auto redeemscript0 = Bitcoin::pk_to_scriptpk(session0.get_output_key());
|
||||
auto redeemscript2 = Bitcoin::pk_to_scriptpk(session2.get_output_key());
|
||||
|
||||
assert(memcmp(&redeemscript0[0], &redeemscript2[0], 32) == 0);
|
||||
|
||||
session0.load_pubnonce(pn1, pk1);
|
||||
session0.load_pubnonce(pn2, pk2);
|
||||
session1.load_pubnonce(pn0, pk0);
|
||||
session1.load_pubnonce(pn2, pk2);
|
||||
session2.load_pubnonce(pn0, pk0);
|
||||
session2.load_pubnonce(pn1, pk1);
|
||||
|
||||
session0.aggregate_pubnonces();
|
||||
session1.aggregate_pubnonces();
|
||||
session2.aggregate_pubnonces();
|
||||
|
||||
session0.sign_partial();
|
||||
session1.sign_partial();
|
||||
session2.sign_partial();
|
||||
|
||||
auto part0 = session0.serialize_partial();
|
||||
auto part1 = session1.serialize_partial();
|
||||
auto part2 = session2.serialize_partial();
|
||||
session0.load_partial(part1, pk1);
|
||||
session0.load_partial(part2, pk2);
|
||||
session1.load_partial(part0, pk0);
|
||||
session1.load_partial(part2, pk2);
|
||||
session2.load_partial(part0, pk0);
|
||||
session2.load_partial(part1, pk1);
|
||||
|
||||
session0.verify_part_sigs();
|
||||
session1.verify_part_sigs();
|
||||
session2.verify_part_sigs();
|
||||
|
||||
auto tweakedagg_xonly = session0.get_output_key();
|
||||
|
||||
std::uint8_t aggsig_buffer[64];
|
||||
session0.aggregate_partials(aggsig_buffer);
|
||||
auto sig0 = SchnorrSig::from_buffer(aggsig_buffer);
|
||||
|
||||
assert(sig0.valid(tweakedagg_xonly, sighash));
|
||||
|
||||
return 0;
|
||||
}
|
||||
91
tests/secp256k1/bip341sighash.cpp
Normal file
91
tests/secp256k1/bip341sighash.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#undef NDEBUG
|
||||
#include"Bitcoin/Tx.hpp"
|
||||
#include"Bitcoin/TxOut.hpp"
|
||||
#include"Bitcoin/sighash.hpp"
|
||||
#include"Ln/Amount.hpp"
|
||||
#include"Sha256/HasherStream.hpp"
|
||||
#include"Util/Str.hpp"
|
||||
#include<assert.h>
|
||||
|
||||
std::string rawtx = "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d";
|
||||
|
||||
std::vector<std::string> spk =
|
||||
{"512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
|
||||
"5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
|
||||
"76a914751e76e8199196d454941c45d1b3a323f1433bd688ac",
|
||||
"5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
|
||||
"512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
|
||||
"00147dd65592d0ab2fe0d0257d571abf032cd9db93dc",
|
||||
"512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
|
||||
"5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5",
|
||||
"512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220"};
|
||||
|
||||
std::vector<uint64_t> ams = {
|
||||
420000000,
|
||||
462000000,
|
||||
294000000,
|
||||
504000000,
|
||||
630000000,
|
||||
378000000,
|
||||
672000000,
|
||||
546000000,
|
||||
588000000
|
||||
};
|
||||
|
||||
Bitcoin::SighashFlags flags[9] = {
|
||||
Bitcoin::SIGHASH_SINGLE,
|
||||
(Bitcoin::SighashFlags) int{131}, /* SIGHASH_SINGLE|SIGHASH_ANYONECANPAY */
|
||||
Bitcoin::SIGHASH_ALL,
|
||||
Bitcoin::SIGHASH_ALL,
|
||||
Bitcoin::SIGHASH_DEFAULT,
|
||||
Bitcoin::SIGHASH_ALL,
|
||||
Bitcoin::SIGHASH_NONE,
|
||||
(Bitcoin::SighashFlags) int{130}, /* SIGHASH_NONE|SIGHASH_ANYONECANPAY */
|
||||
(Bitcoin::SighashFlags) int{129} /* SIGHASH_ALL|SIGHASH_ANYONECANPAY */
|
||||
};
|
||||
|
||||
Sha256::Hash expected[9] = {
|
||||
Sha256::Hash("2514a6272f85cfa0f45eb907fcb0d121b808ed37c6ea160a5a9046ed5526d555"),
|
||||
Sha256::Hash("325a644af47e8a5a2591cda0ab0723978537318f10e6a63d4eed783b96a71a4d"),
|
||||
Sha256::Hash("0000000000000000000000000000000000000000000000000000000000000000"), /* <-- HACK */
|
||||
Sha256::Hash("bf013ea93474aa67815b1b6cc441d23b64fa310911d991e713cd34c7f5d46669"),
|
||||
Sha256::Hash("4f900a0bae3f1446fd48490c2958b5a023228f01661cda3496a11da502a7f7ef"),
|
||||
Sha256::Hash("0000000000000000000000000000000000000000000000000000000000000000"), /* <-- HACK */
|
||||
Sha256::Hash("15f25c298eb5cdc7eb1d638dd2d45c97c4c59dcaec6679cfc16ad84f30876b85"),
|
||||
Sha256::Hash("cd292de50313804dabe4685e83f923d2969577191a3e1d2882220dca88cbeb10"),
|
||||
Sha256::Hash("cccb739eca6c13a8a89e6e5cd317ffe55669bbda23f2fd37b0f18755e008edd2")
|
||||
};
|
||||
|
||||
int main() {
|
||||
auto tx = Bitcoin::Tx(rawtx);
|
||||
auto spkvec = std::vector<std::vector<std::uint8_t>>(spk.size());
|
||||
|
||||
for (size_t i{0}; i < spk.size(); ++i) {
|
||||
auto buf = Util::Str::hexread(spk[i]);
|
||||
spkvec[i] = std::vector<std::uint8_t>(buf.size() +1);
|
||||
spkvec[i][0] = buf.size();
|
||||
for (size_t j{1}, k{0}; k < buf.size(); ++j, ++k)
|
||||
spkvec[i][j] = buf[k];
|
||||
}
|
||||
|
||||
auto amsvec = std::vector<Ln::Amount>(ams.size());
|
||||
for (size_t i{0}; i < ams.size(); ++i)
|
||||
amsvec[i] = Ln::Amount::sat(ams[i]);
|
||||
|
||||
auto sighashes = std::vector<Sha256::Hash>(spkvec.size());
|
||||
for (size_t i{0}; i < spkvec.size(); ++i) {
|
||||
sighashes[i] = Bitcoin::p2tr_sighash( tx
|
||||
, flags[i]
|
||||
, i
|
||||
, amsvec
|
||||
, spkvec
|
||||
, Bitcoin::KEYPATH
|
||||
);
|
||||
if (i == 2 || i == 5) /* <-- HACK */
|
||||
continue;
|
||||
|
||||
assert(expected[i] == sighashes[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue