@vjudeu: I think some example is worth sharing:
you can run regtest
./bitcoin-qt -regtest
mine 101 blocks
generatetodescriptor 101 "raw(51)#8lvh9jxk"
move the coinbase reward from the first block into the output with the same script (this is your "to_spend" transaction)
This is our coinbase transaction for the first block. It will be identical for those commands above, only block headers may be different, but it doesn't matter in our use case:
decoderawtransaction 020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0200f2052a0100000001510000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000
{
"txid": "b4ba2b24be456aacaf743be5fe5de25eb3ebebb52f3faf75aecf45921a810101",
"hash": "8afd56e9e7ddc7760553b4d75eb197f674921297a8d25c8a67dd171393810603",
"version": 2,
"size": 146,
"vsize": 119,
"weight": 476,
"locktime": 0,
"vin": [
{
"coinbase": "5100",
"txinwitness": [
"0000000000000000000000000000000000000000000000000000000000000000"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 50.00000000,
"n": 0,
"scriptPubKey": {
"asm": "1",
"desc": "raw(51)#8lvh9jxk",
"hex": "51",
"type": "nonstandard"
}
},
{
"value": 0.00000000,
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf9",
"desc": "raw(6a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf9)#cav96mf3",
"hex": "6a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf9",
"type": "nulldata"
}
}
]
}
We import some kind of descriptor, which we want to use. For example, this is 2-of-2 multisig with the first two public keys, with well-known private keys:
importdescriptors "[{\"desc\":\"wsh(multi(2,cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA,cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87K7XCyj5v))#yp9440fj\",\"timestamp\":\"now\",\"label\":\"multisig\"}]"
Then, we can get our destination address for that descriptor:
getaddressesbylabel "multisig"
{
"bcrt1qnwvyc7aw8m7acw3lpgs0lqdlaz0drls8luf72cs5nmn9f0kcghdsr03hg4": {
"purpose": "receive"
}
}
Now, we can send those coins into that output:
createrawtransaction "[{\"txid\":\"b4ba2b24be456aacaf743be5fe5de25eb3ebebb52f3faf75aecf45921a810101\",\"vout\":0}]" "[{\"bcrt1qnwvyc7aw8m7acw3lpgs0lqdlaz0drls8luf72cs5nmn9f0kcghdsr03hg4\":0.00000000}]" 0 true
02000000010101811a9245cfae75af3f2fb5ebebb35ee25dfee53b74afac6a45be242bbab40000000000fdffffff0100000000000000002200209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db00000000
This is sufficient as our "to_spend" transaction. But we can add something more, and put for example some OP_RETURN, and describe here the purpose of our signature, include SHA-256 of some message to be signed, or do anything else. For example, if we want to sign an empty message, then we can do it like that:
SHA-256("")=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
SHA-256(0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855)=5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456
createrawtransaction "[{\"txid\":\"b4ba2b24be456aacaf743be5fe5de25eb3ebebb52f3faf75aecf45921a810101\",\"vout\":0}]" "[{\"bcrt1qnwvyc7aw8m7acw3lpgs0lqdlaz0drls8luf72cs5nmn9f0kcghdsr03hg4\":0.00000000},{\"data\":\"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456\"}]" 0 true
02000000010101811a9245cfae75af3f2fb5ebebb35ee25dfee53b74afac6a45be242bbab40000000000fdffffff0200000000000000002200209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db0000000000000000226a205df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945600000000
And then, when we decode it, we can check, if we are satisfied with our "to_spend" transaction or not:
decoderawtransaction 02000000010101811a9245cfae75af3f2fb5ebebb35ee25dfee53b74afac6a45be242bbab40000000000fdffffff0200000000000000002200209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db0000000000000000226a205df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945600000000
{
"txid": "13f688efceedf677e3498a764e9ba0b8dcb744e77afa2bb5e5b3eb23e1005531",
"hash": "13f688efceedf677e3498a764e9ba0b8dcb744e77afa2bb5e5b3eb23e1005531",
"version": 2,
"size": 137,
"vsize": 137,
"weight": 548,
"locktime": 0,
"vin": [
{
"txid": "b4ba2b24be456aacaf743be5fe5de25eb3ebebb52f3faf75aecf45921a810101",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 0.00000000,
"n": 0,
"scriptPubKey": {
"asm": "0 9b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db",
"desc": "addr(bcrt1qnwvyc7aw8m7acw3lpgs0lqdlaz0drls8luf72cs5nmn9f0kcghdsr03hg4)#sncal8mg",
"hex": "00209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db",
"address": "bcrt1qnwvyc7aw8m7acw3lpgs0lqdlaz0drls8luf72cs5nmn9f0kcghdsr03hg4",
"type": "witness_v0_scripthash"
}
},
{
"value": 0.00000000,
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN 5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",
"desc": "raw(6a205df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456)#d92ky5p2",
"hex": "6a205df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",
"type": "nulldata"
}
}
]
}
and then spend it (this is your "to_sign" transaction)
createrawtransaction "[{\"txid\":\"13f688efceedf677e3498a764e9ba0b8dcb744e77afa2bb5e5b3eb23e1005531\",\"vout\":0}]" "[{\"data\":\"00\"}]" 0 true
0200000001315500e123ebb3e5b52bfa7ae744b7dcb8a09b4e768a49e377f6edceef88f6130000000000fdffffff010000000000000000036a010000000000
decoderawtransaction 0200000001315500e123ebb3e5b52bfa7ae744b7dcb8a09b4e768a49e377f6edceef88f6130000000000fdffffff010000000000000000036a010000000000
{
"txid": "b607fce6ad2f066fc7f44da0242bf34ae5bc3356ad0cd2f0ab80c02b9146b15d",
"hash": "b607fce6ad2f066fc7f44da0242bf34ae5bc3356ad0cd2f0ab80c02b9146b15d",
"version": 2,
"size": 63,
"vsize": 63,
"weight": 252,
"locktime": 0,
"vin": [
{
"txid": "13f688efceedf677e3498a764e9ba0b8dcb744e77afa2bb5e5b3eb23e1005531",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 0.00000000,
"n": 0,
"scriptPubKey": {
"asm": "OP_RETURN 0",
"desc": "raw(6a0100)#80h597s4",
"hex": "6a0100",
"type": "nulldata"
}
}
]
}
We are almost there. The first transaction is something, which is ready to be broadcasted, because it spends some input with OP_TRUE, so we don't have to sign it (but we can pick a different coinbase script, to have well-separated networks, if we need it). The second one is something, which can be signed as usual:
signrawtransactionwithwallet "0200000001315500e123ebb3e5b52bfa7ae744b7dcb8a09b4e768a49e377f6edceef88f6130000000000fdffffff010000000000000000036a010000000000" "[{\"txid\":\"13f688efceedf677e3498a764e9ba0b8dcb744e77afa2bb5e5b3eb23e1005531\",\"vout\":0,\"scriptPubKey\":\"00209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db\",\"amount\":0.00000000}]"
{
"hex": "02000000000101315500e123ebb3e5b52bfa7ae744b7dcb8a09b4e768a49e377f6edceef88f6130000000000fdffffff010000000000000000036a01000400473044022052e36a84fd710284a5cc7dfef5f0b2ceb3d8040fb2b0246dec6b047cda0558bc02201d1417dfd5f365979bf48b8ac1270620b7f8d61c1d9f5469dabe6504d5f42fb201473044022024dfa28b7b862a77e3b8e5932c701f48611c807442af58a647044b49d5ce9b46022048b21abd7a00b90f84a4add20708ae6646b06f7192ebb00bd62555a799c5b3eb014752210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee552ae00000000",
"complete": true
}
So, we got it. Now, we mine a new block with both transactions to see, if they will be included or not:
generateblock "raw(51)#8lvh9jxk" "[\"02000000010101811a9245cfae75af3f2fb5ebebb35ee25dfee53b74afac6a45be242bbab40000000000fdffffff0200000000000000002200209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db0000000000000000226a205df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945600000000\",\"02000000000101315500e123ebb3e5b52bfa7ae744b7dcb8a09b4e768a49e377f6edceef88f6130000000000fdffffff010000000000000000036a01000400473044022052e36a84fd710284a5cc7dfef5f0b2ceb3d8040fb2b0246dec6b047cda0558bc02201d1417dfd5f365979bf48b8ac1270620b7f8d61c1d9f5469dabe6504d5f42fb201473044022024dfa28b7b862a77e3b8e5932c701f48611c807442af58a647044b49d5ce9b46022048b21abd7a00b90f84a4add20708ae6646b06f7192ebb00bd62555a799c5b3eb014752210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee552ae00000000\"]"
{
"hash": "33d04181986f72e6eb6ec242038ccc771ab91737824842af7e8b59fc2b1e062e"
}
And then, we can get our block:
getblock 33d04181986f72e6eb6ec242038ccc771ab91737824842af7e8b59fc2b1e062e 0
00000020835d62e738fa39592a010cec55ecf2893fe34892366a0b16d4412b8145f8265d87e0b04d65bc32299be9c2a3d7e2e4893f611be09e838a23e9f5b6e85063d78974d33366ffff7f200000000003020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03016600ffffffff0200f2052a0100000001510000000000000000266a24aa21a9ed82c0bae1305d92344ce05f23f4ae166f8fb643ff66105e9653e0f27213ab068e012000000000000000000000000000000000000000000000000000000000000000000000000002000000010101811a9245cfae75af3f2fb5ebebb35ee25dfee53b74afac6a45be242bbab40000000000fdffffff0200000000000000002200209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db0000000000000000226a205df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c94560000000002000000000101315500e123ebb3e5b52bfa7ae744b7dcb8a09b4e768a49e377f6edceef88f6130000000000fdffffff010000000000000000036a01000400473044022052e36a84fd710284a5cc7dfef5f0b2ceb3d8040fb2b0246dec6b047cda0558bc02201d1417dfd5f365979bf48b8ac1270620b7f8d61c1d9f5469dabe6504d5f42fb201473044022024dfa28b7b862a77e3b8e5932c701f48611c807442af58a647044b49d5ce9b46022048b21abd7a00b90f84a4add20708ae6646b06f7192ebb00bd62555a799c5b3eb014752210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee552ae00000000
Voila. This is our signature. For the recipient, we have to share only the first, and the last command. Which means, that if you want to verify a signature, then all you have to do, is to execute those two commands in some empty regtest network:
generatetodescriptor 101 "raw(51)#8lvh9jxk"
generateblock "raw(51)#8lvh9jxk" "[\"02000000010101811a9245cfae75af3f2fb5ebebb35ee25dfee53b74afac6a45be242bbab40000000000fdffffff0200000000000000002200209b984c7bae3efddc3a3f0a20ff81bfe89ed1fe07ff13e562149ee654bed845db0000000000000000226a205df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945600000000\",\"02000000000101315500e123ebb3e5b52bfa7ae744b7dcb8a09b4e768a49e377f6edceef88f6130000000000fdffffff010000000000000000036a01000400473044022052e36a84fd710284a5cc7dfef5f0b2ceb3d8040fb2b0246dec6b047cda0558bc02201d1417dfd5f365979bf48b8ac1270620b7f8d61c1d9f5469dabe6504d5f42fb201473044022024dfa28b7b862a77e3b8e5932c701f48611c807442af58a647044b49d5ce9b46022048b21abd7a00b90f84a4add20708ae6646b06f7192ebb00bd62555a799c5b3eb014752210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee552ae00000000\"]"
{
"hash": "3f87546d0aa236dd53d9900658768bc975af30eaadba65d5b7b78a2a653374f2"
}
As you can see, the hash of the block will be different each time, mainly because of timestamps. But: if you explore the contents of the block, then it contains exactly the same "to_spend" and "to_sign" transactions. And if you import your multisig descriptor, you will see your "to_spend" transaction in the GUI (if you want to also see "to_sign" transaction, then you have to add some additional output, which will contain one of the keys from your wallet).
importdescriptors "[{\"desc\":\"wsh(multi(2,cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA,cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87K7XCyj5v))#yp9440fj\",\"timestamp\":\"now\",\"label\":\"multisig\"}]"
So, as you can see, you can create some transactions, which are similar to BIP-322, even if slightly different, if you just use regtest for that purpose. And I think it should meet most of your requirements, as long as BIP-322 is not finished.