mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-05 10:42:13 +02:00
Merge bitcoin/bitcoin#29032: signet: omit commitment for some trivial challenges
6ee32aaacatest: signet tool genpsbt and solvepsbt commands (Sjors Provoost)0a99d99fe4signet: miner skips PSBT step for OP_TRUE (Sjors Provoost)cdfb70e5a6signet: split decode_psbt miner helper (Sjors Provoost) Pull request description: [BIP325](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki) mentions the following rule: > In the special case where an empty solution is valid (ie scriptSig and scriptWitness are both empty) this additional commitment can optionally be left out. This special case is to allow non-signet-aware block generation code to be used to test a custom signet chain where the challenge is trivially true. Such a signet can be created using e.g. `-signetchallenge=51` (`OP_TRUE`). However `contrib/signet/miner` won't omit the commitment. This PR improves the miner by skipping the PSBT for known trivial scripts (just `OP_TRUE` and trivial pushes for now). This prevents it from appending the 4 byte signet header to the witness commitment, as allowed by the above rule. --- Previously the script would fail with `PSBT signing failed`, making it difficult to mine. This is no longer the case. ACKs for top commit: achow101: ACK6ee32aaacatheStack: re-ACK6ee32aaacadanielabrozzoni: ACK6ee32aaacaTree-SHA512: e47fbf471f2909286a6c1c073799ea388b9c19551afcce96cf9af45cc48d25c02f1e48e08861a88b604361e2c107a759d5baf393da8a37360de419f31651758a
This commit is contained in:
@@ -80,3 +80,4 @@ These steps can instead be done explicitly:
|
||||
|
||||
This is intended to allow you to replace part of the pipeline for further experimentation (eg, to sign the block with a hardware wallet).
|
||||
|
||||
For custom signets with a trivial challenge such as `OP_TRUE` and `OP_2` the walletprocesspsbt step can be skipped.
|
||||
|
||||
@@ -18,7 +18,7 @@ PATH_BASE_CONTRIB_SIGNET = os.path.abspath(os.path.dirname(os.path.realpath(__fi
|
||||
PATH_BASE_TEST_FUNCTIONAL = os.path.abspath(os.path.join(PATH_BASE_CONTRIB_SIGNET, "..", "..", "test", "functional"))
|
||||
sys.path.insert(0, PATH_BASE_TEST_FUNCTIONAL)
|
||||
|
||||
from test_framework.blocktools import get_witness_script, script_BIP34_coinbase_height # noqa: E402
|
||||
from test_framework.blocktools import get_witness_script, script_BIP34_coinbase_height, SIGNET_HEADER # noqa: E402
|
||||
from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_binary, from_hex, ser_string, ser_uint256, tx_from_hex, MAX_SEQUENCE_NONFINAL # noqa: E402
|
||||
from test_framework.psbt import PSBT, PSBTMap, PSBT_GLOBAL_UNSIGNED_TX, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE # noqa: E402
|
||||
from test_framework.script import CScript, CScriptOp # noqa: E402
|
||||
@@ -28,7 +28,6 @@ logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
datefmt='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
SIGNET_HEADER = b"\xec\xc7\xda\xa2"
|
||||
PSBT_SIGNET_BLOCK = b"\xfc\x06signetb" # proprietary PSBT global field holding the block being signed
|
||||
RE_MULTIMINER = re.compile(r"^(\d+)(-(\d+))?/(\d+)$")
|
||||
|
||||
@@ -66,22 +65,31 @@ def signet_txs(block, challenge):
|
||||
|
||||
return spend, to_spend
|
||||
|
||||
def decode_psbt(b64psbt):
|
||||
def decode_challenge_psbt(b64psbt):
|
||||
psbt = PSBT.from_base64(b64psbt)
|
||||
|
||||
assert len(psbt.tx.vin) == 1
|
||||
assert len(psbt.tx.vout) == 1
|
||||
assert PSBT_SIGNET_BLOCK in psbt.g.map
|
||||
return psbt
|
||||
|
||||
def get_block_from_psbt(psbt):
|
||||
return from_binary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK])
|
||||
|
||||
def get_solution_from_psbt(psbt, emptyok=False):
|
||||
scriptSig = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"")
|
||||
scriptWitness = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTWITNESS, b"\x00")
|
||||
|
||||
return from_binary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK]), ser_string(scriptSig) + scriptWitness
|
||||
if emptyok and len(scriptSig) == 0 and scriptWitness == b"\x00":
|
||||
return None
|
||||
return ser_string(scriptSig) + scriptWitness
|
||||
|
||||
def finish_block(block, signet_solution, grind_cmd):
|
||||
block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution)
|
||||
block.vtx[0].rehash()
|
||||
block.hashMerkleRoot = block.calc_merkle_root()
|
||||
if signet_solution is None:
|
||||
pass # Don't need to add a signet commitment if there's no signet signature needed
|
||||
else:
|
||||
block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution)
|
||||
block.vtx[0].rehash()
|
||||
block.hashMerkleRoot = block.calc_merkle_root()
|
||||
if grind_cmd is None:
|
||||
block.solve()
|
||||
else:
|
||||
@@ -93,10 +101,7 @@ def finish_block(block, signet_solution, grind_cmd):
|
||||
block.rehash()
|
||||
return block
|
||||
|
||||
def generate_psbt(tmpl, reward_spk, *, blocktime=None, poolid=None):
|
||||
signet_spk = tmpl["signet_challenge"]
|
||||
signet_spk_bin = bytes.fromhex(signet_spk)
|
||||
|
||||
def new_block(tmpl, reward_spk, *, blocktime=None, poolid=None):
|
||||
scriptSig = script_BIP34_coinbase_height(tmpl["height"])
|
||||
if poolid is not None:
|
||||
scriptSig = CScript(b"" + scriptSig + CScriptOp.encode_op_pushdata(poolid))
|
||||
@@ -125,8 +130,14 @@ def generate_psbt(tmpl, reward_spk, *, blocktime=None, poolid=None):
|
||||
block.vtx[0].wit.vtxinwit = [cbwit]
|
||||
block.vtx[0].vout.append(CTxOut(0, bytes(get_witness_script(witroot, witnonce))))
|
||||
|
||||
signme, spendme = signet_txs(block, signet_spk_bin)
|
||||
block.vtx[0].rehash()
|
||||
block.hashMerkleRoot = block.calc_merkle_root()
|
||||
|
||||
return block
|
||||
|
||||
def generate_psbt(block, signet_spk):
|
||||
signet_spk_bin = bytes.fromhex(signet_spk)
|
||||
signme, spendme = signet_txs(block, signet_spk_bin)
|
||||
psbt = PSBT()
|
||||
psbt.g = PSBTMap( {PSBT_GLOBAL_UNSIGNED_TX: signme.serialize(),
|
||||
PSBT_SIGNET_BLOCK: block.serialize()
|
||||
@@ -175,12 +186,16 @@ def get_reward_addr_spk(args, height):
|
||||
def do_genpsbt(args):
|
||||
poolid = get_poolid(args)
|
||||
tmpl = json.load(sys.stdin)
|
||||
signet_spk = tmpl["signet_challenge"]
|
||||
_, reward_spk = get_reward_addr_spk(args, tmpl["height"])
|
||||
psbt = generate_psbt(tmpl, reward_spk, poolid=poolid)
|
||||
block = new_block(tmpl, reward_spk, poolid=poolid)
|
||||
psbt = generate_psbt(block, signet_spk)
|
||||
print(psbt)
|
||||
|
||||
def do_solvepsbt(args):
|
||||
block, signet_solution = decode_psbt(sys.stdin.read())
|
||||
psbt = decode_challenge_psbt(sys.stdin.read())
|
||||
block = get_block_from_psbt(psbt)
|
||||
signet_solution = get_solution_from_psbt(psbt, emptyok=True)
|
||||
block = finish_block(block, signet_solution, args.grind_cmd)
|
||||
print(block.serialize().hex())
|
||||
|
||||
@@ -223,6 +238,21 @@ def seconds_to_hms(s):
|
||||
out = "-" + out
|
||||
return out
|
||||
|
||||
def trivial_challenge(spkhex):
|
||||
"""
|
||||
BIP325 allows omitting the signet commitment when scriptSig and
|
||||
scriptWitness are both empty. This is the case for trivial
|
||||
challenges such as OP_TRUE or a single data push.
|
||||
"""
|
||||
spk = bytes.fromhex(spkhex)
|
||||
if len(spk) == 1 and 0x51 <= spk[0] <= 0x60:
|
||||
# OP_TRUE/OP_1...OP_16
|
||||
return True
|
||||
elif 2 <= len(spk) <= 76 and spk[0] + 1 == len(spk):
|
||||
# Single fixed push of 1-75 bytes
|
||||
return True
|
||||
return False
|
||||
|
||||
class Generate:
|
||||
INTERVAL = 600.0*2016/2015 # 10 minutes, adjusted for the off-by-one bug
|
||||
|
||||
@@ -323,14 +353,22 @@ class Generate:
|
||||
return tmpl
|
||||
|
||||
def mine(self, bcli, grind_cmd, tmpl, reward_spk):
|
||||
psbt = generate_psbt(tmpl, reward_spk, blocktime=self.mine_time, poolid=self.poolid)
|
||||
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
|
||||
psbt_signed = json.loads(bcli("-stdin", "walletprocesspsbt", input=input_stream))
|
||||
if not psbt_signed.get("complete",False):
|
||||
logging.debug("Generated PSBT: %s" % (psbt,))
|
||||
sys.stderr.write("PSBT signing failed\n")
|
||||
return None
|
||||
block, signet_solution = decode_psbt(psbt_signed["psbt"])
|
||||
block = new_block(tmpl, reward_spk, blocktime=self.mine_time, poolid=self.poolid)
|
||||
|
||||
signet_spk = tmpl["signet_challenge"]
|
||||
if trivial_challenge(signet_spk):
|
||||
signet_solution = None
|
||||
else:
|
||||
psbt = generate_psbt(block, signet_spk)
|
||||
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
|
||||
psbt_signed = json.loads(bcli("-stdin", "walletprocesspsbt", input=input_stream))
|
||||
if not psbt_signed.get("complete",False):
|
||||
logging.debug("Generated PSBT: %s" % (psbt,))
|
||||
sys.stderr.write("PSBT signing failed\n")
|
||||
return None
|
||||
psbt = decode_challenge_psbt(psbt_signed["psbt"])
|
||||
signet_solution = get_solution_from_psbt(psbt)
|
||||
|
||||
return finish_block(block, signet_solution, grind_cmd)
|
||||
|
||||
def do_generate(args):
|
||||
|
||||
Reference in New Issue
Block a user