Salvatore Ingala [ARCHIVE] on Nostr: π Original date posted:2023-05-01 ποΈ Summary of this message: The author ...
π
Original date posted:2023-05-01
ποΈ Summary of this message: The author suggests that games where all possible futures can be enumerated are not ideal to showcase MATT, and proposes rock-paper-scissors as an academic example. They provide a protocol for playing the game and explain how it can be implemented with CICV/COCV.
π Original message:Hi Johan,
Thanks for your message.
I think games where all the possible futures can be enumerated are
not ideal to showcase MATT, as one could just fully represent them
with just CTV or COCV, and not use the "data embedding" at all.
Perhaps rock-paper-scissors could be a better academic example. [1]
I'm not sure this will fully address your question; however I think
it's quite an instructive example, and I wanted to work it out for
quite some time.
It would be interesting to explore some contracts where the size
of the embedded data is substantially larger, and that could be
a natural next step to think about.
### Rock paper scissors
We want a protocol between Alice and Bob, where they bet 1 coin each:
1. Alice chooses and publishes her move;
2. Bob chooses his move, and the pot is adjudicated as per the rules.
Of course, if implemented naively, this wouldn't be a very fun game:
Bob would just wait to see Alice's move and play accordingly.
That's easy to fix, though:
1. Alice publishes a commitment to her move
2. Bob publishes his move in clear
3. Alice reveals her move, and the pot is adjudicated.
We can encode Rock = 0, Paper = 1, Scissors = 2. Let m_A, m_B be
Alice's and Bob's move, respectively. Then, it's easy to verify that:
β m_B - m_A == 0 (mod 3) ==> it's a tie
β m_B - m_A == 1 (mod 3) ==> Bob wins
β m_B - m_A == 2 (mod 3) ==> Alice wins
In order to create a hiding commitment for Alice, she can choose a
256-bit random number r_A, and compute:
c_A = SHA256(m_A || r_A)
With that in mind, the full protocol can go like this:
1. Alice chooses her move m_A and a large random number r_A;
she posts c_A computed as above;
2. Bob chooses m_B and publishes it;
3. Alice publishes m_A and r_A, then the winner is adjudicated.
### MATT playing RPS
To implement this with CICV/COCV, we can use just 3 transactions: in
fact, Alice can already compute c_A and share it with Bob before they
both commit their coins into an encumbered UTXO. That also means that
c_A can actually be hardcoded in the Scripts, rather than taking
space in the UTXO's embedded data.
Therefore, they both put one coin each, and they send to an output
whose script is the state S0 described below.
We assume that the keypath in the P2TR defined below is either a NUMS
point, or perhaps a Musig2 aggregate key that can be used to settle
the game collaboratively.
Note that there are 3 possible payout options that are fully known
when the game starts: either Alice takes all the money, or they split
evenly, or Bob takes all the money.
Similarly to the vault implementation [2], this seems to be another
case where CTV fits very well, as it allows to very efficiently
describe the three possible outcomes by their CTV hashes. Let them
be <ctv-alice-wins>, <ctv-split>, <ctv-bob-wins>, respectively.
Therefore, this avoids the need for 64-bit maths, and explicit amount
introspection β at least for these contracts.
[State S0] (Start of the game, Alice moved; Bob's turn)
Spending conditions:
- after <forfait-delay>, Alice takes the money // (Bob forfaits)
- Bob posts m_B (0, 1 or 2); the next output is [S1] with data m_B
The first script is:
// witness: []
<forfait-delay>
OP_CHECKSEQUENCEVERIFY
OP_DROP
<ctv-alice-wins>
OP_CHECKTEMPLATEVERIFY
The second is
// witness: [<bob_sig> <m_B>]
OP_DUP 0 3 OP_WITHIN // check that m_B is 0, 1 or 2
<internal_pubkey> OP_SWAP
<S1's taptree>
OP_CHECKOUTPUTCONTRACTVERIFY // check that the output is correct
<bob_pubkey>
OP_CHECKSIG
[State S1] (Alice reveals m_A and adjudicates)
- after <forfait-timeout>, Bob takes the money // (Alice forfaits)
- Alice posts correct m_A and r_A compatible with c_A;
The first script is symmetric to Bob's forfait script above.
The second condition can be split into three leaf scripts, one for
each possible value of m_B - m_A (mod 3):
// witness: [<m_B> <m_A> <r_A>]
OP_OVER OP_DUP OP_TOALTSTACK // save m_A
0 3 OP_WITHIN OP_VERIFY // check that m_A is 0, 1 or 2
// check that SHA256(m_A || r_A) equals c_A
OP_2DUP
OP_CAT OP_SHA256
<c_A>
OP_EQUALVERIFY
OP_DUP
<internal_pubkey>, OP_SWAP
OP_CHECKINPUTCONTRACTVERIFY
OP_FROMALTSTACK
OP_SUB // stack now contains m_B - m_A
OP_DUP // if the result is negative, add 3
0 OP_LESSTHAN
OP_IF
3
OP_ADD
OP_ENDIF
{0, 1, 2} // draw / Bob wins / Alice wins, respectively
OP_EQUALVERIFY
{<ctv-split>, <ctv-bob-wins>, <ctv-alice-wins>} // respectively
OP_CHECKTEMPLATEVERIFY
### Comments
In general, we would have to worry about the possible
malleability of the witness elements, when they are not signatures
or preimages themselves. Here, in particular, it might seem that's
an issue when <m_B> is provided while spending the state [S0].
However, here the value of <m_B> is also committed to in the output
thanks to COCV; therefore, Bob's signature prevents malleability
also for m_B.
In general, it seems to be the case in MATT contracts that one would
want the signature of the authorized party performing a transition to
some other state of the smart contract with contains embedded data;
this makes the malleability issue less of a problem in practice than
I initially thought.
If the internal_pubkey is a musig-aggregated key of Alice and Bob,
the game can be settled entirely offline after the first transaction.
Simply, Bob communicates his move to Alice, Alice reveals her move to
Bob, and they can settle the bet. The game would be played without
any script being executed, therefore all transactions could look like
any other P2TR, with the only possible fingerprinting being due to the
input amounts.
It should be possible to generalize the protocol so that many rounds
can be played off-chain within the same UTXO, but I didn't try to
figure out the details.
Best,
Salvatore Ingala
[1] - https://en.wikipedia.org/wiki/Rock_paper_scissors
[2] -
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-April/021588.html
On Fri, 28 Apr 2023 at 10:48, Johan TorΓ₯s Halseth <johanth at gmail.com> wrote:
> Hi, Salvatore.
>
> I find this proposal very interesting. Especially since you seemingly
> can achieve such powerful capabilities by such simple opcodes.
>
> I'm still trying to grok how this would look like on-chain (forget
> about the off-chain part for now), if we were to play out such a
> computation.
>
> Let's say you have a simple game like "one player tic-tac-toe" with
> only two tiles: [ _ | _ ]. The player wins if he can get two in a row
> (pretty easy game tbh).
>
> Could you give a complete example how you would encode one such state
> transition (going from [ X, _ ] -> [ X, X ] for instance) in Bitcoin
> script?
>
> Feel free to choose a different game or program if you prefer :)
>
> Thanks!
> Johan
>
>
>
> On Tue, Dec 13, 2022 at 2:08β―PM Billy Tetrud via bitcoin-dev
> <bitcoin-dev at lists.linuxfoundation.org> wrote:
> >
> > Re Verkle trees, that's a very interesting construction that would be
> super useful as a tool for something like Utreexo. A potentially
> substantial downside is that it seems the cryptography used to get those
> nice properties of Verkle trees isn't quantum safe. While a lot of things
> in Bitcoin seems to be going down the path of quantum-unsafe (I'm looking
> at you, taproot), there are still a lot of people who think quantum safety
> is important in a lot of contexts.
> >
> > On Thu, Dec 1, 2022 at 5:52 AM Salvatore Ingala via bitcoin-dev <
> bitcoin-dev at lists.linuxfoundation.org> wrote:
> >>
> >> Hello Rijndael,
> >>
> >>
> >>
> >> On Wed, 30 Nov 2022 at 23:09, Rijndael <rot13maxi at protonmail.com>
> wrote:
> >>>
> >>> Hello Salvatore,
> >>>
> >>> I found my answer re-reading your original post:
> >>> > During the arbitration phase (say at the i-th leaf node of M_T), any
> party can win the challenge by providing correct values for tr_i = (st_i,
> op_i, st_{i + 1}). Crucially, only one party is able to provide correct
> values, and Script can verify that indeed the state moves from st_i to
> st_{i + 1} by executing op_i. The challenge is over.
> >>
> >> You are correct, the computation step encoded in a leaf needs to be
> simple enough for Script to verify it.
> >>
> >> For the academic purpose of proving completeness (that is, any
> computation can be successfully "proved" by the availability of the
> corresponding fraud proof), one can imagine reducing the computation all
> the way down to a circuit, where each step (leaf) is as simple as what can
> be checked with {OP_NOT, OP_BOOLAND, OP_BOOLOR, OP_EQUAL}.
> >>
> >> In practice, you would want to utilize Script to its fullest, so for
> example you wouldn't compile a SHA256 computation to something else β you'd
> rather use OP_SHA256 directly.
> >>
> >>>
> >>> That raises leads to a different question: Alice initially posts a
> commitment to an execution trace of `f(x) = y`, `x`, and `y`. Bob Disagrees
> with `y` so starts the challenge protocol. Is there a commitment to `f`? In
> other words, the dispute protocol (as I read it) finds the leftmost step in
> Alice and Bob's execution traces that differ, and then rewards the coins to
> the participant who's "after-value" is computed by the step's operation
> applied to the "before value". But if the participants each present valid
> steps but with different operations, who wins? In other words, Alice could
> present [64, DECREMENT, 63] and Bob could present [64, INCREMENT, 65].
> Those steps don't match, but both are valid. Is there something to ensure
> that before the challenge protocol starts, that the execution trace that
> Alice posts is for the right computation and not a different computation
> that yields a favorable result for her (and for which she can generate a
> valid merkle tree)?
> >>
> >>
> >> The function f is already hard-coded in the contract itself, by means
> of the tree of scripts β that already commits to the possible futures.
> Therefore, once you are at state S14, you know that you are verifying the
> 6th step of the computation; and the operation in the 6th step of the
> computation depends solely on f, not its inputs. In fact, you made me
> realize that I could drop op_i from the i-th leaf commitment, and just
> embed the information in the Script of that corresponding state.
> >>
> >> Note that the states S0 to S14 of the 256x game are not _all_ the
> possible states, but only the ones that occurred in that execution of the
> contract (corresponding to a path from the root to the leaf of the Merkle
> tree of the computation trace), and therefore the ones that materialized in
> a UTXO. Different choices made by the parties (by providing different data,
> and therefore choosing different branches) would lead to a different leaf,
> and therefore to different (but in a certain sense "symmetric") states.
> >>
> >> ========
> >>
> >> Since we are talking about the fact that f is committed to in the
> contract, I'll take the chance to extend on this a bit with a fun
> construction on top.
> >> It is well-known in the academic literature of state channels that you
> can create contracts where even the function ("program", or "contract") is
> not decided when the channel is created.
> >>
> >> Since f is generic, we can choose f itself to be a universal Turing
> machine. That is, we can imagine a function f(code, data) that executes a
> program ("code") on the "data" given to it as input.
> >> Since we can do fraud proofs on statements "f(code, data) == output",
> we could build contracts where the "code" itself is chosen later.
> >>
> >> For example, one could build a universal state channel, where parties
> can enter any contract among themselves (e.g.: start playing a chess game)
> entirely inside the channel. The state of this universal channel would
> contain all the states of the individual contracts that are currently open
> in the channel, and even starting/closing contracts can happen entirely
> off-chain.
> >>
> >> I believe these constructions are practical (the code of universal
> Turing machines is not really complicated), so it might be worth exploring
> further to figure out useful applications of this approach (supercharging
> lightning?).
> >>
> >> We should probably start by implementing testnet rock-paper-scissors in
> MATT, though :)
> >>
> >> Best,
> >> Salvatore Ingala
> >> _______________________________________________
> >> bitcoin-dev mailing list
> >> bitcoin-dev at lists.linuxfoundation.org
> >> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
> >
> > _______________________________________________
> > bitcoin-dev mailing list
> > bitcoin-dev at lists.linuxfoundation.org
> > https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.linuxfoundation.org/pipermail/bitcoin-dev/attachments/20230501/2f7ae5a8/attachment-0001.html>
ποΈ Summary of this message: The author suggests that games where all possible futures can be enumerated are not ideal to showcase MATT, and proposes rock-paper-scissors as an academic example. They provide a protocol for playing the game and explain how it can be implemented with CICV/COCV.
π Original message:Hi Johan,
Thanks for your message.
I think games where all the possible futures can be enumerated are
not ideal to showcase MATT, as one could just fully represent them
with just CTV or COCV, and not use the "data embedding" at all.
Perhaps rock-paper-scissors could be a better academic example. [1]
I'm not sure this will fully address your question; however I think
it's quite an instructive example, and I wanted to work it out for
quite some time.
It would be interesting to explore some contracts where the size
of the embedded data is substantially larger, and that could be
a natural next step to think about.
### Rock paper scissors
We want a protocol between Alice and Bob, where they bet 1 coin each:
1. Alice chooses and publishes her move;
2. Bob chooses his move, and the pot is adjudicated as per the rules.
Of course, if implemented naively, this wouldn't be a very fun game:
Bob would just wait to see Alice's move and play accordingly.
That's easy to fix, though:
1. Alice publishes a commitment to her move
2. Bob publishes his move in clear
3. Alice reveals her move, and the pot is adjudicated.
We can encode Rock = 0, Paper = 1, Scissors = 2. Let m_A, m_B be
Alice's and Bob's move, respectively. Then, it's easy to verify that:
β m_B - m_A == 0 (mod 3) ==> it's a tie
β m_B - m_A == 1 (mod 3) ==> Bob wins
β m_B - m_A == 2 (mod 3) ==> Alice wins
In order to create a hiding commitment for Alice, she can choose a
256-bit random number r_A, and compute:
c_A = SHA256(m_A || r_A)
With that in mind, the full protocol can go like this:
1. Alice chooses her move m_A and a large random number r_A;
she posts c_A computed as above;
2. Bob chooses m_B and publishes it;
3. Alice publishes m_A and r_A, then the winner is adjudicated.
### MATT playing RPS
To implement this with CICV/COCV, we can use just 3 transactions: in
fact, Alice can already compute c_A and share it with Bob before they
both commit their coins into an encumbered UTXO. That also means that
c_A can actually be hardcoded in the Scripts, rather than taking
space in the UTXO's embedded data.
Therefore, they both put one coin each, and they send to an output
whose script is the state S0 described below.
We assume that the keypath in the P2TR defined below is either a NUMS
point, or perhaps a Musig2 aggregate key that can be used to settle
the game collaboratively.
Note that there are 3 possible payout options that are fully known
when the game starts: either Alice takes all the money, or they split
evenly, or Bob takes all the money.
Similarly to the vault implementation [2], this seems to be another
case where CTV fits very well, as it allows to very efficiently
describe the three possible outcomes by their CTV hashes. Let them
be <ctv-alice-wins>, <ctv-split>, <ctv-bob-wins>, respectively.
Therefore, this avoids the need for 64-bit maths, and explicit amount
introspection β at least for these contracts.
[State S0] (Start of the game, Alice moved; Bob's turn)
Spending conditions:
- after <forfait-delay>, Alice takes the money // (Bob forfaits)
- Bob posts m_B (0, 1 or 2); the next output is [S1] with data m_B
The first script is:
// witness: []
<forfait-delay>
OP_CHECKSEQUENCEVERIFY
OP_DROP
<ctv-alice-wins>
OP_CHECKTEMPLATEVERIFY
The second is
// witness: [<bob_sig> <m_B>]
OP_DUP 0 3 OP_WITHIN // check that m_B is 0, 1 or 2
<internal_pubkey> OP_SWAP
<S1's taptree>
OP_CHECKOUTPUTCONTRACTVERIFY // check that the output is correct
<bob_pubkey>
OP_CHECKSIG
[State S1] (Alice reveals m_A and adjudicates)
- after <forfait-timeout>, Bob takes the money // (Alice forfaits)
- Alice posts correct m_A and r_A compatible with c_A;
The first script is symmetric to Bob's forfait script above.
The second condition can be split into three leaf scripts, one for
each possible value of m_B - m_A (mod 3):
// witness: [<m_B> <m_A> <r_A>]
OP_OVER OP_DUP OP_TOALTSTACK // save m_A
0 3 OP_WITHIN OP_VERIFY // check that m_A is 0, 1 or 2
// check that SHA256(m_A || r_A) equals c_A
OP_2DUP
OP_CAT OP_SHA256
<c_A>
OP_EQUALVERIFY
OP_DUP
<internal_pubkey>, OP_SWAP
OP_CHECKINPUTCONTRACTVERIFY
OP_FROMALTSTACK
OP_SUB // stack now contains m_B - m_A
OP_DUP // if the result is negative, add 3
0 OP_LESSTHAN
OP_IF
3
OP_ADD
OP_ENDIF
{0, 1, 2} // draw / Bob wins / Alice wins, respectively
OP_EQUALVERIFY
{<ctv-split>, <ctv-bob-wins>, <ctv-alice-wins>} // respectively
OP_CHECKTEMPLATEVERIFY
### Comments
In general, we would have to worry about the possible
malleability of the witness elements, when they are not signatures
or preimages themselves. Here, in particular, it might seem that's
an issue when <m_B> is provided while spending the state [S0].
However, here the value of <m_B> is also committed to in the output
thanks to COCV; therefore, Bob's signature prevents malleability
also for m_B.
In general, it seems to be the case in MATT contracts that one would
want the signature of the authorized party performing a transition to
some other state of the smart contract with contains embedded data;
this makes the malleability issue less of a problem in practice than
I initially thought.
If the internal_pubkey is a musig-aggregated key of Alice and Bob,
the game can be settled entirely offline after the first transaction.
Simply, Bob communicates his move to Alice, Alice reveals her move to
Bob, and they can settle the bet. The game would be played without
any script being executed, therefore all transactions could look like
any other P2TR, with the only possible fingerprinting being due to the
input amounts.
It should be possible to generalize the protocol so that many rounds
can be played off-chain within the same UTXO, but I didn't try to
figure out the details.
Best,
Salvatore Ingala
[1] - https://en.wikipedia.org/wiki/Rock_paper_scissors
[2] -
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-April/021588.html
On Fri, 28 Apr 2023 at 10:48, Johan TorΓ₯s Halseth <johanth at gmail.com> wrote:
> Hi, Salvatore.
>
> I find this proposal very interesting. Especially since you seemingly
> can achieve such powerful capabilities by such simple opcodes.
>
> I'm still trying to grok how this would look like on-chain (forget
> about the off-chain part for now), if we were to play out such a
> computation.
>
> Let's say you have a simple game like "one player tic-tac-toe" with
> only two tiles: [ _ | _ ]. The player wins if he can get two in a row
> (pretty easy game tbh).
>
> Could you give a complete example how you would encode one such state
> transition (going from [ X, _ ] -> [ X, X ] for instance) in Bitcoin
> script?
>
> Feel free to choose a different game or program if you prefer :)
>
> Thanks!
> Johan
>
>
>
> On Tue, Dec 13, 2022 at 2:08β―PM Billy Tetrud via bitcoin-dev
> <bitcoin-dev at lists.linuxfoundation.org> wrote:
> >
> > Re Verkle trees, that's a very interesting construction that would be
> super useful as a tool for something like Utreexo. A potentially
> substantial downside is that it seems the cryptography used to get those
> nice properties of Verkle trees isn't quantum safe. While a lot of things
> in Bitcoin seems to be going down the path of quantum-unsafe (I'm looking
> at you, taproot), there are still a lot of people who think quantum safety
> is important in a lot of contexts.
> >
> > On Thu, Dec 1, 2022 at 5:52 AM Salvatore Ingala via bitcoin-dev <
> bitcoin-dev at lists.linuxfoundation.org> wrote:
> >>
> >> Hello Rijndael,
> >>
> >>
> >>
> >> On Wed, 30 Nov 2022 at 23:09, Rijndael <rot13maxi at protonmail.com>
> wrote:
> >>>
> >>> Hello Salvatore,
> >>>
> >>> I found my answer re-reading your original post:
> >>> > During the arbitration phase (say at the i-th leaf node of M_T), any
> party can win the challenge by providing correct values for tr_i = (st_i,
> op_i, st_{i + 1}). Crucially, only one party is able to provide correct
> values, and Script can verify that indeed the state moves from st_i to
> st_{i + 1} by executing op_i. The challenge is over.
> >>
> >> You are correct, the computation step encoded in a leaf needs to be
> simple enough for Script to verify it.
> >>
> >> For the academic purpose of proving completeness (that is, any
> computation can be successfully "proved" by the availability of the
> corresponding fraud proof), one can imagine reducing the computation all
> the way down to a circuit, where each step (leaf) is as simple as what can
> be checked with {OP_NOT, OP_BOOLAND, OP_BOOLOR, OP_EQUAL}.
> >>
> >> In practice, you would want to utilize Script to its fullest, so for
> example you wouldn't compile a SHA256 computation to something else β you'd
> rather use OP_SHA256 directly.
> >>
> >>>
> >>> That raises leads to a different question: Alice initially posts a
> commitment to an execution trace of `f(x) = y`, `x`, and `y`. Bob Disagrees
> with `y` so starts the challenge protocol. Is there a commitment to `f`? In
> other words, the dispute protocol (as I read it) finds the leftmost step in
> Alice and Bob's execution traces that differ, and then rewards the coins to
> the participant who's "after-value" is computed by the step's operation
> applied to the "before value". But if the participants each present valid
> steps but with different operations, who wins? In other words, Alice could
> present [64, DECREMENT, 63] and Bob could present [64, INCREMENT, 65].
> Those steps don't match, but both are valid. Is there something to ensure
> that before the challenge protocol starts, that the execution trace that
> Alice posts is for the right computation and not a different computation
> that yields a favorable result for her (and for which she can generate a
> valid merkle tree)?
> >>
> >>
> >> The function f is already hard-coded in the contract itself, by means
> of the tree of scripts β that already commits to the possible futures.
> Therefore, once you are at state S14, you know that you are verifying the
> 6th step of the computation; and the operation in the 6th step of the
> computation depends solely on f, not its inputs. In fact, you made me
> realize that I could drop op_i from the i-th leaf commitment, and just
> embed the information in the Script of that corresponding state.
> >>
> >> Note that the states S0 to S14 of the 256x game are not _all_ the
> possible states, but only the ones that occurred in that execution of the
> contract (corresponding to a path from the root to the leaf of the Merkle
> tree of the computation trace), and therefore the ones that materialized in
> a UTXO. Different choices made by the parties (by providing different data,
> and therefore choosing different branches) would lead to a different leaf,
> and therefore to different (but in a certain sense "symmetric") states.
> >>
> >> ========
> >>
> >> Since we are talking about the fact that f is committed to in the
> contract, I'll take the chance to extend on this a bit with a fun
> construction on top.
> >> It is well-known in the academic literature of state channels that you
> can create contracts where even the function ("program", or "contract") is
> not decided when the channel is created.
> >>
> >> Since f is generic, we can choose f itself to be a universal Turing
> machine. That is, we can imagine a function f(code, data) that executes a
> program ("code") on the "data" given to it as input.
> >> Since we can do fraud proofs on statements "f(code, data) == output",
> we could build contracts where the "code" itself is chosen later.
> >>
> >> For example, one could build a universal state channel, where parties
> can enter any contract among themselves (e.g.: start playing a chess game)
> entirely inside the channel. The state of this universal channel would
> contain all the states of the individual contracts that are currently open
> in the channel, and even starting/closing contracts can happen entirely
> off-chain.
> >>
> >> I believe these constructions are practical (the code of universal
> Turing machines is not really complicated), so it might be worth exploring
> further to figure out useful applications of this approach (supercharging
> lightning?).
> >>
> >> We should probably start by implementing testnet rock-paper-scissors in
> MATT, though :)
> >>
> >> Best,
> >> Salvatore Ingala
> >> _______________________________________________
> >> bitcoin-dev mailing list
> >> bitcoin-dev at lists.linuxfoundation.org
> >> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
> >
> > _______________________________________________
> > bitcoin-dev mailing list
> > bitcoin-dev at lists.linuxfoundation.org
> > https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.linuxfoundation.org/pipermail/bitcoin-dev/attachments/20230501/2f7ae5a8/attachment-0001.html>