Skip to content

Commit b1c5c50

Browse files
committed
P2QRH: temporary commit for spending. Needs cleanup. Will rebase this commit
1 parent 215c4c0 commit b1c5c50

File tree

12 files changed

+351
-5
lines changed

12 files changed

+351
-5
lines changed

doc/design/p2qrh.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ The equivalent needs to be implemented for PQC algorithms implemented in [libbit
157157
| | walletcreatefundedpsbt | Creates new PSBTs with P2QRH outputs |
158158
| | utxoupdatepsbt | Updates PSBT data for P2QRH inputs/outputs |
159159
| Raw Transaction Operations | signrawtransactionwithwallet | Signs P2QRH inputs using wallet keys |
160+
| | testmempoolaccept | testmempoolaccept "signedhex" |
161+
| | sendrawtransaction | sendrawtransaction "signedhex" |
160162
| | createrawtransaction| createrawtransaction '[]' '{"bc1r...":0.01}'
161163
| | signrawtransactionwithkey | Signs P2QRH inputs with specified keys |
162164
| | decoderawtransaction | Decodes transactions with P2QRH inputs/outputs |
@@ -200,3 +202,98 @@ The equivalent needs to be implemented for PQC algorithms implemented in [libbit
200202
```
201203
$ build/test/functional/rpc_validateaddress.py
202204
```
205+
206+
## Modifications
207+
208+
### bad-txns-nonstandard-inputs
209+
210+
```
211+
$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
212+
[
213+
{
214+
"txid": "824244091bece2eb03a6f0dec6c8f87619dc687f10f4da03465cefd27c3007f7",
215+
"wtxid": "9f3f2fbe411d6c69b1882bbf884124511592f888d2129b105f5d577cd2bfa917",
216+
"allowed": false,
217+
"reject-reason": "bad-txns-nonstandard-inputs",
218+
"reject-details": "bad-txns-nonstandard-inputs"
219+
}
220+
]
221+
```
222+
223+
To allow for P2QRH (witness v3, similar to P2TR) funding transactions, you need to update the policy logic so that P2QRH outputs are considered standard, just like P2TR (Taproot) outputs.
224+
What needs to change:
225+
226+
1. In AreInputsStandard, currently only witness v0 and v1 (P2WPKH, P2WSH, P2TR) are considered standard. P2QRH (witness v3) is not, so funding transactions to P2QRH are not allowed.
227+
1. In IsStandard, the Solver function returns WITNESS_UNKNOWN for any witness version it doesn't recognize, including v3. This means P2QRH outputs are not considered standard.
228+
229+
How to fix:
230+
231+
1. Update Solver to recognize P2QRH as a standard type.
232+
** Add a new TxoutType for P2QRH (e.g., WITNESS_V3_P2QRH).
233+
** In Solver, if witnessversion == 3 && witnessprogram.size() == WITNESS_V3_P2QRH_SIZE, return TxoutType::WITNESS_V3_P2QRH.
234+
1. Update IsStandard to allow P2QRH outputs.
235+
** Accept TxoutType::WITNESS_V3_P2QRH as standard.
236+
1. Update AreInputsStandard to allow spending from P2QRH outputs.
237+
** Accept TxoutType::WITNESS_V3_P2QRH as standard, not just WITNESS_V1_TAPROOT.
238+
239+
240+
### Witness version reserved for soft-fork upgrades
241+
242+
```
243+
$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
244+
[
245+
{
246+
"txid": "824244091bece2eb03a6f0dec6c8f87619dc687f10f4da03465cefd27c3007f7",
247+
"wtxid": "9f3f2fbe411d6c69b1882bbf884124511592f888d2129b105f5d577cd2bfa917",
248+
"allowed": false,
249+
"reject-reason": "non-mandatory-script-verify-flag (Witness version reserved for soft-fork upgrades)",
250+
"reject-details": "non-mandatory-script-verify-flag (Witness version reserved for soft-fork upgrades), input 0 of 824244091bece2eb03a6f0dec6c8f87619dc687f10f4da03465cefd27c3007f7 (wtxid 9f3f2fbe411d6c69b1882bbf884124511592f888d2129b105f5d577cd2bfa917), spending 6745235727bf162d190553f7da087d8484b73716f2fcf774b8c56245f5f9e619:0"
251+
}
252+
]
253+
```
254+
255+
### Invalid Taproot control block size
256+
257+
```
258+
$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
259+
[
260+
{
261+
"txid": "824244091bece2eb03a6f0dec6c8f87619dc687f10f4da03465cefd27c3007f7",
262+
"wtxid": "9f3f2fbe411d6c69b1882bbf884124511592f888d2129b105f5d577cd2bfa917",
263+
"allowed": false,
264+
"reject-reason": "mandatory-script-verify-flag-failed (Invalid Taproot control block size)",
265+
"reject-details": "mandatory-script-verify-flag-failed (Invalid Taproot control block size), input 0 of 824244091bece2eb03a6f0dec6c8f87619dc687f10f4da03465cefd27c3007f7 (wtxid 9f3f2fbe411d6c69b1882bbf884124511592f888d2129b105f5d577cd2bfa917), spending 6745235727bf162d190553f7da087d8484b73716f2fcf774b8c56245f5f9e619:0"
266+
}
267+
]
268+
```
269+
270+
### Taproot version reserved for soft-fork upgrades
271+
272+
```
273+
$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
274+
[
275+
{
276+
"txid": "61829641ab32b14a65de0d9e93feedcdd1ee7b5e40d01c43a4ca72815fa886a8",
277+
"wtxid": "eeaaf921f323be39e2b8314bd4ebe7275b784eac9697634bbff975e735961213",
278+
"allowed": false,
279+
"reject-reason": "non-mandatory-script-verify-flag (Taproot version reserved for soft-fork upgrades)",
280+
"reject-details": "non-mandatory-script-verify-flag (Taproot version reserved for soft-fork upgrades), input 0 of 61829641ab32b14a65de0d9e93feedcdd1ee7b5e40d01c43a4ca72815fa886a8 (wtxid eeaaf921f323be39e2b8314bd4ebe7275b784eac9697634bbff975e735961213), spending 6745235727bf162d190553f7da087d8484b73716f2fcf774b8c56245f5f9e619:0"
281+
}
282+
]
283+
```
284+
285+
### Stack size must be exactly one after execution
286+
287+
```
288+
$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
289+
[
290+
{
291+
"txid": "61829641ab32b14a65de0d9e93feedcdd1ee7b5e40d01c43a4ca72815fa886a8",
292+
"wtxid": "eeaaf921f323be39e2b8314bd4ebe7275b784eac9697634bbff975e735961213",
293+
"allowed": false,
294+
"reject-reason": "mandatory-script-verify-flag-failed (Stack size must be exactly one after execution)",
295+
"reject-details": "mandatory-script-verify-flag-failed (Stack size must be exactly one after execution), input 0 of 61829641ab32b14a65de0d9e93feedcdd1ee7b5e40d01c43a4ca72815fa886a8 (wtxid eeaaf921f323be39e2b8314bd4ebe7275b784eac9697634bbff975e735961213), spending 6745235727bf162d190553f7da087d8484b73716f2fcf774b8c56245f5f9e619:0"
296+
}
297+
]
298+
299+
```

src/addresstype.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
8787
addressRet = tap;
8888
return true;
8989
}
90+
case TxoutType::WITNESS_V3_P2QRH: {
91+
WitnessV3P2QRH p2qrh;
92+
std::copy(vSolutions[0].begin(), vSolutions[0].end(), p2qrh.begin());
93+
addressRet = p2qrh;
94+
return true;
95+
}
9096
case TxoutType::ANCHOR: {
9197
addressRet = PayToAnchor();
9298
return true;

src/policy/policy.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType)
9191
return false;
9292
if (m < 1 || m > n)
9393
return false;
94+
} else if (whichType == TxoutType::WITNESS_V3_P2QRH) {
95+
// Accept as standard
96+
return true;
9497
}
9598

9699
return true;
@@ -207,6 +210,9 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
207210
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
208211
return false;
209212
}
213+
} else if (whichType == TxoutType::WITNESS_V3_P2QRH) {
214+
// Accept as standard
215+
continue;
210216
}
211217
}
212218

@@ -298,6 +304,34 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
298304
return false;
299305
}
300306
}
307+
308+
// Check policy limits for P2QRH spends:
309+
// - MAX_STANDARD_P2QRH_STACK_ITEM_SIZE limit for stack item size
310+
// - Script path only (no key path spending)
311+
// - No annexes
312+
if (witnessversion == 3 && witnessprogram.size() == WITNESS_V3_P2QRH_SIZE) {
313+
// P2QRH spend (non-P2SH-wrapped, version 3, witness program size 32)
314+
std::span stack{tx.vin[i].scriptWitness.stack};
315+
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
316+
// Annexes are nonstandard as long as no semantics are defined for them.
317+
return false;
318+
}
319+
if (stack.size() >= 2) {
320+
// Script path spend (2 or more stack elements after removing optional annex)
321+
const auto& control_block = SpanPopBack(stack);
322+
SpanPopBack(stack); // Ignore script
323+
if (control_block.empty()) return false; // Empty control block is invalid
324+
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
325+
// Leaf version 0xc0 (aka Tapscript, see BIP 342)
326+
for (const auto& item : stack) {
327+
if (item.size() > MAX_STANDARD_P2QRH_STACK_ITEM_SIZE) return false;
328+
}
329+
}
330+
} else {
331+
// P2QRH only supports script path spending, no key path spending allowed
332+
return false;
333+
}
334+
}
301335
}
302336
return true;
303337
}

src/policy/policy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS{100};
5050
static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE{80};
5151
/** The maximum size in bytes of each witness stack item in a standard BIP 342 script (Taproot, leaf version 0xc0) */
5252
static constexpr unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE{80};
53+
/** The maximum size in bytes of each witness stack item in a standard P2QRH script (Quantum-Resistant-Hash) */
54+
static constexpr unsigned int MAX_STANDARD_P2QRH_STACK_ITEM_SIZE{8000};
5355
/** The maximum size in bytes of a standard witnessScript */
5456
static constexpr unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE{3600};
5557
/** The maximum size of a standard ScriptSig */

src/pubkey.cpp

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <algorithm>
1919
#include <cassert>
20+
#include <cstdio> // Add this include for printf
2021

2122
using namespace util::hex_literals;
2223

@@ -230,9 +231,128 @@ bool XOnlyPubKey::IsFullyValid() const
230231
bool XOnlyPubKey::VerifySchnorr(const uint256& msg, std::span<const unsigned char> sigbytes) const
231232
{
232233
assert(sigbytes.size() == 64);
234+
235+
// Log the input parameters
236+
printf("XOnlyPubKey::VerifySchnorr - Input parameters:\n");
237+
printf(" sigbytes.size(): %zu\n", sigbytes.size());
238+
printf(" msg (hex): ");
239+
for (int i = 0; i < 32; i++) {
240+
printf("%02x", msg.begin()[i]);
241+
}
242+
printf("\n");
243+
printf(" sigbytes (hex): ");
244+
for (size_t i = 0; i < sigbytes.size(); i++) {
245+
printf("%02x", sigbytes[i]);
246+
}
247+
printf("\n");
248+
printf(" m_keydata (hex): ");
249+
for (size_t i = 0; i < m_keydata.size(); i++) {
250+
printf("%02x", m_keydata.data()[i]);
251+
}
252+
printf("\n");
253+
254+
// Check if the public key is valid
255+
printf(" IsFullyValid(): %s\n", IsFullyValid() ? "true" : "false");
256+
233257
secp256k1_xonly_pubkey pubkey;
234-
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data())) return false;
235-
return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey);
258+
bool parse_result = secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data());
259+
printf(" secp256k1_xonly_pubkey_parse result: %s\n", parse_result ? "true" : "false");
260+
261+
if (!parse_result) {
262+
printf(" FAILED: Could not parse xonly pubkey\n");
263+
return false;
264+
}
265+
266+
// Log the parsed pubkey data
267+
unsigned char serialized_pubkey[32];
268+
secp256k1_xonly_pubkey_serialize(secp256k1_context_static, serialized_pubkey, &pubkey);
269+
printf(" parsed pubkey (hex): ");
270+
for (int i = 0; i < 32; i++) {
271+
printf("%02x", serialized_pubkey[i]);
272+
}
273+
printf("\n");
274+
275+
// Check if the signature bytes look valid (non-zero)
276+
bool sig_has_nonzero = false;
277+
for (size_t i = 0; i < sigbytes.size(); i++) {
278+
if (sigbytes[i] != 0) {
279+
sig_has_nonzero = true;
280+
break;
281+
}
282+
}
283+
printf(" signature has non-zero bytes: %s\n", sig_has_nonzero ? "true" : "false");
284+
285+
// Check if the message hash looks valid (non-zero)
286+
bool msg_has_nonzero = false;
287+
for (int i = 0; i < 32; i++) {
288+
if (msg.begin()[i] != 0) {
289+
msg_has_nonzero = true;
290+
break;
291+
}
292+
}
293+
printf(" message hash has non-zero bytes: %s\n", msg_has_nonzero ? "true" : "false");
294+
295+
// Log the first few bytes of signature components for debugging
296+
printf(" signature R (first 16 bytes): ");
297+
for (int i = 0; i < 16; i++) {
298+
printf("%02x", sigbytes[i]);
299+
}
300+
printf("\n");
301+
printf(" signature S (first 16 bytes): ");
302+
for (int i = 32; i < 48; i++) {
303+
printf("%02x", sigbytes[i]);
304+
}
305+
printf("\n");
306+
307+
// Log the exact parameters being passed to secp256k1_schnorrsig_verify
308+
printf(" Calling secp256k1_schnorrsig_verify with:\n");
309+
printf(" context: %p\n", (void*)secp256k1_context_static);
310+
printf(" sig64: %p (first byte: %02x)\n", (void*)sigbytes.data(), sigbytes[0]);
311+
printf(" msg: %p (first byte: %02x)\n", (void*)msg.begin(), msg.begin()[0]);
312+
printf(" msglen: 32\n");
313+
printf(" pubkey: %p\n", (void*)&pubkey);
314+
315+
bool verify_result = secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey);
316+
printf(" secp256k1_schnorrsig_verify result: %s\n", verify_result ? "true" : "false");
317+
318+
// Try with a different message to see if it's a message issue
319+
uint256 zero_msg;
320+
zero_msg.SetNull();
321+
bool zero_verify_result = secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), zero_msg.begin(), 32, &pubkey);
322+
printf(" secp256k1_schnorrsig_verify with zero message: %s\n", zero_verify_result ? "true" : "false");
323+
324+
// Try with a different signature to see if it's a signature issue
325+
unsigned char zero_sig[64] = {0};
326+
bool zero_sig_verify_result = secp256k1_schnorrsig_verify(secp256k1_context_static, zero_sig, msg.begin(), 32, &pubkey);
327+
printf(" secp256k1_schnorrsig_verify with zero signature: %s\n", zero_sig_verify_result ? "true" : "false");
328+
329+
// Try with byte-reversed message to check for endianness issues
330+
unsigned char reversed_msg[32];
331+
for (int i = 0; i < 32; i++) {
332+
reversed_msg[i] = msg.begin()[31 - i];
333+
}
334+
printf(" reversed msg (hex): ");
335+
for (int i = 0; i < 32; i++) {
336+
printf("%02x", reversed_msg[i]);
337+
}
338+
printf("\n");
339+
bool reversed_verify_result = secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), reversed_msg, 32, &pubkey);
340+
printf(" secp256k1_schnorrsig_verify with reversed message: %s\n", reversed_verify_result ? "true" : "false");
341+
342+
// Try with byte-reversed signature to check for signature endianness
343+
unsigned char reversed_sig[64];
344+
for (int i = 0; i < 64; i++) {
345+
reversed_sig[i] = sigbytes[63 - i];
346+
}
347+
printf(" reversed sig (first 16 bytes): ");
348+
for (int i = 0; i < 16; i++) {
349+
printf("%02x", reversed_sig[i]);
350+
}
351+
printf("\n");
352+
bool reversed_sig_verify_result = secp256k1_schnorrsig_verify(secp256k1_context_static, reversed_sig, msg.begin(), 32, &pubkey);
353+
printf(" secp256k1_schnorrsig_verify with reversed signature: %s\n", reversed_sig_verify_result ? "true" : "false");
354+
355+
return verify_result;
236356
}
237357

238358
static const HashWriter HASHER_TAPTWEAK{TaggedHash("TapTweak")};

src/rpc/rawtransaction.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ static RPCHelpMan decodescript()
547547
case TxoutType::SCRIPTHASH:
548548
case TxoutType::WITNESS_UNKNOWN:
549549
case TxoutType::WITNESS_V1_TAPROOT:
550+
case TxoutType::WITNESS_V3_P2QRH:
550551
case TxoutType::ANCHOR:
551552
// Should not be wrapped
552553
return false;
@@ -590,6 +591,7 @@ static RPCHelpMan decodescript()
590591
case TxoutType::WITNESS_V0_KEYHASH:
591592
case TxoutType::WITNESS_V0_SCRIPTHASH:
592593
case TxoutType::WITNESS_V1_TAPROOT:
594+
case TxoutType::WITNESS_V3_P2QRH:
593595
case TxoutType::ANCHOR:
594596
// Should not be wrapped
595597
return false;

0 commit comments

Comments
 (0)