summaryrefslogtreecommitdiff
path: root/f0/751b02cf53a5ff0642e6c38d70696adbab165c
blob: 317573b87ffacba93c5e6635652e0782a768f4f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
Return-Path: <antoine.riard@gmail.com>
Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133])
 by lists.linuxfoundation.org (Postfix) with ESMTP id 477FFC000D
 for <bitcoin-dev@lists.linuxfoundation.org>;
 Fri, 10 Sep 2021 04:12:43 +0000 (UTC)
Received: from localhost (localhost [127.0.0.1])
 by smtp2.osuosl.org (Postfix) with ESMTP id 2F7BD40101
 for <bitcoin-dev@lists.linuxfoundation.org>;
 Fri, 10 Sep 2021 04:12:43 +0000 (UTC)
X-Virus-Scanned: amavisd-new at osuosl.org
X-Spam-Flag: NO
X-Spam-Score: -1.098
X-Spam-Level: 
X-Spam-Status: No, score=-1.098 tagged_above=-999 required=5
 tests=[ADVANCE_FEE_4_NEW=1, BAYES_00=-1.9, DKIM_SIGNED=0.1,
 DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 FREEMAIL_FROM=0.001, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001,
 SPF_HELO_NONE=0.001, SPF_PASS=-0.001] autolearn=no autolearn_force=no
Authentication-Results: smtp2.osuosl.org (amavisd-new);
 dkim=pass (2048-bit key) header.d=gmail.com
Received: from smtp2.osuosl.org ([127.0.0.1])
 by localhost (smtp2.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)
 with ESMTP id tFgQZmQWcvOK
 for <bitcoin-dev@lists.linuxfoundation.org>;
 Fri, 10 Sep 2021 04:12:39 +0000 (UTC)
X-Greylist: whitelisted by SQLgrey-1.8.0
Received: from mail-wr1-x42a.google.com (mail-wr1-x42a.google.com
 [IPv6:2a00:1450:4864:20::42a])
 by smtp2.osuosl.org (Postfix) with ESMTPS id 567DA400EE
 for <bitcoin-dev@lists.linuxfoundation.org>;
 Fri, 10 Sep 2021 04:12:39 +0000 (UTC)
Received: by mail-wr1-x42a.google.com with SMTP id g16so674890wrb.3
 for <bitcoin-dev@lists.linuxfoundation.org>;
 Thu, 09 Sep 2021 21:12:39 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112;
 h=mime-version:references:in-reply-to:from:date:message-id:subject:to;
 bh=RCbjGeynxIRE/XWHVlt/pmkKz40Gw1F4eZXzbDaEzHs=;
 b=gbSgzkPQ1bF1VCOLGjOfgvw427AZEKlEvhNFcTWXQ+E5yL+6IxWfsBp3pXBFJ4N0TC
 k1qNyX0Nbak4xXG0b9noiyqMLEkJWLfJLsnEya2K/4uZzaAWbpDLwKUKswDXxc2SgC4t
 ggjKzPcUryyQA6SwAhYdlQIZUwH/dUdfMptq6UKfXOBPFFUsal5d2Ajwy6oFzRFnxEZt
 tJtW4+ZpJfu1003/qGxLeJn6tmuu6VCcBh8C/LZImyPk53Qxf8e9/D2WFA5hpfm6O9qs
 HB30GXWtrduRqRJm7ZUpTWSf6FxA1C7T7NRxczEDbFHWBau5W7M39vkoWLXq8yttV9pO
 PrCQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20210112;
 h=x-gm-message-state:mime-version:references:in-reply-to:from:date
 :message-id:subject:to;
 bh=RCbjGeynxIRE/XWHVlt/pmkKz40Gw1F4eZXzbDaEzHs=;
 b=yYpQHWQROxmEpHUUhDLIrDcW3NkwRIYq6dMxCvcv7rG1EszwrZtdI7ZZtFEzThymju
 8VKCcLA1vyKoqvyA+P5vMPIws5p4Xy7BG6+dbRPLJ4V30VwnTNNe0eTTVBOOVHq+538h
 bmVbFDtyP6phuuIzW5v7br0dXkuqwVkVPXOiE8x0OP9Epc9khYzT2GuDnCrVSIMHQ1rC
 sfKVBOgpb1PtaxT/UmV4TUS8CZEBT9uNe9DoSQI1IUXyuT6uEZCZ0YFddvwb765C21dQ
 DEtkaXaQwC+g9OLVLcH1seDpC6J7cCetWcdqwzE+cdm8wk2LhFE2/fHnVXU7izr0iphB
 j45A==
X-Gm-Message-State: AOAM532KhCqaZ3KyFcZW5kzxYd+V3U/vAAi+nx/PO6E4cnLmsa/TPpc6
 X1OEFJgzJwq+Ea2qxOVafjpS6NO9kFHnjsMVahXdE72BApc=
X-Google-Smtp-Source: ABdhPJx6c9A4cSTV0RmTgbM5laMvyHq8dDbL82VIJ0jJniBN09Nyt545seeDujf/mpVw3GQCjTSIN5ftKMIOKWoxyfE=
X-Received: by 2002:a5d:4b0b:: with SMTP id v11mr7398110wrq.359.1631247156718; 
 Thu, 09 Sep 2021 21:12:36 -0700 (PDT)
MIME-Version: 1.0
References: <20210909064138.GA22496@erisian.com.au>
In-Reply-To: <20210909064138.GA22496@erisian.com.au>
From: Antoine Riard <antoine.riard@gmail.com>
Date: Fri, 10 Sep 2021 00:12:24 -0400
Message-ID: <CALZpt+FnnbGJC4=KO_OPiKxt0Ey9Bzh1gxP1dQSDz2aBi9WyOA@mail.gmail.com>
To: Anthony Towns <aj@erisian.com.au>, 
 Bitcoin Protocol Discussion <bitcoin-dev@lists.linuxfoundation.org>
Content-Type: multipart/alternative; boundary="00000000000051308105cb9c5339"
X-Mailman-Approved-At: Fri, 10 Sep 2021 08:01:29 +0000
Subject: Re: [bitcoin-dev] TAPLEAF_UPDATE_VERIFY covenant opcode
X-BeenThere: bitcoin-dev@lists.linuxfoundation.org
X-Mailman-Version: 2.1.15
Precedence: list
List-Id: Bitcoin Protocol Discussion <bitcoin-dev.lists.linuxfoundation.org>
List-Unsubscribe: <https://lists.linuxfoundation.org/mailman/options/bitcoin-dev>, 
 <mailto:bitcoin-dev-request@lists.linuxfoundation.org?subject=unsubscribe>
List-Archive: <http://lists.linuxfoundation.org/pipermail/bitcoin-dev/>
List-Post: <mailto:bitcoin-dev@lists.linuxfoundation.org>
List-Help: <mailto:bitcoin-dev-request@lists.linuxfoundation.org?subject=help>
List-Subscribe: <https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev>, 
 <mailto:bitcoin-dev-request@lists.linuxfoundation.org?subject=subscribe>
X-List-Received-Date: Fri, 10 Sep 2021 04:12:43 -0000

--00000000000051308105cb9c5339
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

Hi AJ,

Thanks for finally putting the pieces together! [0]

We've been hacking with Gleb on a paper for the CoinPool protocol [1]
during the last weeks and it should be public soon, hopefully highlighting
what kind of scheme, TAPLEAF_UPDATE_VERIFY-style of covenant enable :)

Here few early feedbacks on this specific proposal,

> So that makes it relatively easy to imagine creating a new taproot addres=
s
> based on the input you're spending by doing some or all of the following:
>
>  * Updating the internal public key (ie from P to P' =3D P + X)
>  * Trimming the merkle path (eg, removing CD)
>  * Removing the script you're currently executing (ie E)
>  * Adding a new step to the end of the merkle path (eg F)

"Talk is cheap. Show me the code" :p

    case OP_MERKLESUB:
    {
        if (!(flags & SCRIPT_VERIFY_MERKLESUB)) {
            break;
        }

        if (stack.size() < 2) {
            return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
        }

        valtype& vchPubKey =3D stacktop(-1);

        if (vchPubKey.size() !=3D 32) {
            break;
        }

        const std::vector<unsigned char>& vch =3D stacktop(-2);
        int nOutputPos =3D CScriptNum(stacktop(-2), fRequireMinimal).getint=
();

        if (nOutputPos < 0) {
            return set_error(serror, SCRIPT_ERR_NEGATIVE_MERKLEVOUT);
        }

        if (!checker.CheckMerkleUpdate(*execdata.m_control, nOutputPos,
vchPubKey)) {
            return set_error(serror, SCRIPT_ERR_UNSATISFIED_MERKLESUB);
        }
        break;
    }

    case OP_NOP1: case OP_NOP5:



    template <class T>
    bool GenericTransactionSignatureChecker<T>::CheckMerkleUpdate(const
std::vector<unsigned char>& control, unsigned int out_pos, const
std::vector<unsigned char>& point) const
    {
        //! The internal pubkey (x-only, so no Y coordinate parity).
        XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() +
1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
        //! Update the internal key by subtracting the point.
        XOnlyPubKey s{uint256(point)};
        XOnlyPubKey u;
        try {
            u =3D p.UpdateInternalKey(s).value();
        } catch (const std::bad_optional_access& e) {
            return false;
        }

        //! The first control node is made the new tapleaf hash.
        //! TODO: what if there is no control node ?
        uint256 updated_tapleaf_hash;
        updated_tapleaf_hash =3D uint256(std::vector<unsigned
char>(control.data() + TAPROOT_CONTROL_BASE_SIZE, control.data() +
TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE));

        //! The committed-to output must be in the spent transaction vout
range.
        if (out_pos >=3D txTo->vout.size()) return false;
        int witnessversion;
        std::vector<unsigned char> witnessprogram;
        txTo->vout[out_pos].scriptPubKey.IsWitnessProgram(witnessversion,
witnessprogram);
        //! The committed to output must be a witness v1 program at least
        if (witnessversion =3D=3D 0) {
            return false;
        } else if (witnessversion =3D=3D 1) {
            //! The committed-to output.
            const XOnlyPubKey q{uint256(witnessprogram)};
            //! Compute the Merkle root from the leaf and the incremented
by one path.
            const uint256 merkle_root =3D ComputeTaprootMerkleRoot(control,
updated_tapleaf_hash, 1);
            //! TODO modify MERKLESUB design
            bool parity_ret =3D q.CheckTapTweak(u, merkle_root, true);
            bool no_parity_ret =3D q.CheckTapTweak(u, merkle_root, false);
            if (!parity_ret && !no_parity_ret) {
                return false;
            }
        }
        return true;
    }


Here the main chunks for an "<n> <point> OP_MERKLESUB" opcode, with `n` the
output position which is checked for update and `point` the x-only pubkey
which must be subtracted from the internal key.

I think one design advantage of explicitly passing the output position as a
stack element is giving more flexibility to your contract dev. The first
output could be SIGHASH_ALL locked-down. e.g "you have to pay Alice on
output 1 while pursuing the contract semantic on output 2".

One could also imagine a list of output positions to force the taproot
update on multiple outputs ("OP_MULTIMERKLESUB"). Taking back your citadel
joint venture example, partners could decide to split the funds in 3
equivalent amounts *while* conserving the pre-negotiated script policies [2=
]

For the merkle branches extension, I was thinking of introducing a separate
OP_MERKLEADD, maybe to *add* a point to the internal pubkey group signer.
If you're only interested in leaf pruning, using OP_MERKLESUB only should
save you one byte of empty vector ?

We can also explore more fancy opcodes where the updated merkle branch is
pushed on the stack for deep manipulations. Or even n-dimensions
inspections if combined with your G'root [3] ?

Note, this current OP_MERKLESUB proposal doesn't deal with committing the
parity of the internal pubkey as part of the spent utxo. As you highlighted
well in your other mail, if we want to conserve the updated key-path across
a sequence of TLUV-covenanted transactions, we need either
a) to select a set of initial points, where whatever combination of
add/sub, it yields an even-y point. Or b) have the even/odd bit
re-committed at each update. Otherwise, we're not guaranteed to cancel the
point from the aggregated key.

This property is important for CoinPool. Let's say you have A+B+C+D, after
the D withdraw transaction has been confirmed on-chain, you want A+B+C to
retain the ability to use the key-path and update the off-chain state,
without forcing a script path spend to a new setup.

If we put the updated internal key parity bit in the first control byte, we
need to have a  redundant commitment somewhere else as we can't trust the
spender to not be willingly to break the key-path spend of the remaining
group of signers.

One solution I was thinking about was introducing a new tapscript version
(`TAPROOT_INTERNAL_TAPSCRIPT`) signaling that VerifyTaprootCommitment must
compute the TapTweak with a new TapTweak=3D(internal_pubkey || merkle_root =
||
parity_bit). A malicious participant wouldn't be able to interfere with the
updated internal key as it would break its own spending taproot commitment
verification ?

> That's useless without some way of verifying that the new utxo retains
> the bitcoin that was in the old utxo, so also include a new opcode
> IN_OUT_AMOUNT that pushes two items onto the stack: the amount from this
> input's utxo, and the amount in the corresponding output, and then expect
> anyone using TLUV to use maths operators to verify that funds are being
> appropriately retained in the updated scriptPubKey.

Credit to you for the SIGHASH_GROUP design, here the code, with
SIGHASH_ANYPUBKEY/ANYAMOUNT extensions.

    if ((output_type & SIGHASH_GROUP) =3D=3D SIGHASH_GROUP) {
        // Verify the output group bounds
        if (execdata.m_bundle->first =3D=3D execdata.m_bundle->second ||
execdata.m_bundle->second >=3D tx_to.vout.size()) return false;

        // Verify the value commitment
        if (VerifyOutputsGroup(tx_to, cache.m_spent_outputs[in_pos].nValue,
execdata.m_bundle->first, execdata.m_bundle->second)) return false;



        for (unsigned int out_pos =3D execdata.m_bundle->first; out_pos <
execdata.m_bundle->second + 1; out_pos++) {
            bool anypubkey_flag =3D false;
            bool anyamount_flag =3D false;
            std::map<unsigned int, char>::const_iterator it;

            if ((output_type & SIGHASH_GROUP_ANYPUBKEY) =3D=3D
SIGHASH_GROUP_ANYPUBKEY) {
                it =3D execdata.m_anypubkeys.find(out_pos);
                if (it !=3D execdata.m_anypubkeys.end() && it->second =3D=
=3D 1) {
                    anypubkey_flag =3D true;
                }
            }

            if ((output_type & SIGHASH_GROUP_ANYAMOUNT) =3D=3D
SIGHASH_GROUP_ANYAMOUNT) {
                it =3D execdata.m_anyamounts.find(out_pos);
                if (it !=3D execdata.m_anyamounts.end() && it->second =3D=
=3D 1) {
                    anyamount_flag =3D true;
                }
            }

            if (!anypubkey_flag) {
                ss << tx_to.vout[out_pos].scriptPubKey;
            }
            if (!anyamount_flag) {
                ss << tx_to.vout[out_pos].nValue;
            }

        }
    }

I think it's achieving the same effect as IN_OUT_AMOUNT, at least for
CoinPool use-case. A MuSig  `contract_pubkey` can commit to the
`to_withdraw` output while allowing a wildcard for the `to_pool` output
nValue/scriptPubKey. The nValue correctness will be ensured by the
group-value-lock validation rule (`VerifyOutputsGroup`) and scriptPubkey by
OP_MERKLESUB commitment.

I think witness data size it's roughly equivalent as the annex fields must
be occupied by the output group commitment. SIGHASH_GROUP might be more
flexible than IN_OUT_AMOUNT for a range of use-cases, see my point on AMM.

> The second scheme is allowing for a utxo to represent a group's pooled
> funds. The idea being that as long as everyone's around you can use
> the taproot key path to efficiently move money around within the pool,
> or use a single transaction and signature for many people in the pool
> to make payments. But key path spends only work if everyone's available
> to sign -- what happens if someone disappears, or loses access to their
> keys, or similar? For that, we want to have script paths to allow other
> people to reclaim their funds even if everyone else disappears. So we
> setup scripts for each participant, eg for Alice:
>
>  * The tx is signed by Alice
>  * The output value must be at least the input value minus Alice's balanc=
e
>  * Must pass TLUV such that:
>    + the internal public key is the old internal pubkey minus Alice's key
>    + the currently executing script is dropped from the merkle path
>    + no steps are otherwise removed or added

Yes the security model is roughly similar to the LN one. Instead of a
counter-signed commitment transaction which can be broadcast at any point
during channel lifetime, you have a pre-signed withdraw transaction sending
to {`to_withdraw`,`to_pool`} outputs. Former is your off-chain balance, the
latter one is the pool balance, and one grieved with the updated Taproot
output. The withdraw tapscript force the point subtraction with the
following format (`<n> <withdraw_point> <OP_MERKLESUB> <33-byte
contract_pubkey> OP_CHECKSIG)

> A simpler case for something like this might be for funding a joint
> venture -- suppose you're joining with some other early bitcoiners to
> buy land to build a citadel, so you each put 20 BTC into a pooled utxo,
> ready to finalise the land purchase in a few months, but you also want
> to make sure you can reclaim the funds if the deal falls through. So
> you might include scripts like the above that allow you to reclaim your
> balance, but add a CLTV condition preventing anyone from doing that until
> the deal's deadline has passed. If the deal goes ahead, you all transfer
> the funds to the vendor via the keypath; if it doesn't work out, you
> hopefully return your funds via the keypath, but if things turn really
> sour, you can still just directly reclaim your 20 BTC yourself via the
> script path.

Yes, that kind of blockchain validation semantic extension is vaudoo-magic
if we want to enable smart corporation/scalable multi-event contracts. I
gave a presentation on advanced bitcoin contracts two years ago, mentioning
we would need covenants to solve the factorial complexity on edge-case [4]

Bitcoin ledger would fit perfectly well to host international commerce law
style of contracts, where you have a lot of usual fancy provisions (e.g
hardship, delay penalty, ...) :)

> First it can't tweak scripts in areas of the merkle tree that it can't
> see -- I don't see a way of doing that particularly efficiently, so maybe
> it's best just to leave that as something for the people responsible for
> the funds to negotiate via the keypath, in which case it's automatically
> both private and efficient since all the details stay off-chain, anyway

Yeah, in that kind of case, we might want to push the merkle root as a
stack element but still update the internal pubkey from the spent utxo ?
This new merkle_root would be the tree of tweaked scripts as you expect
them if you execute *this* tapscript. And you can still this new tree with
a tapbranch inherited from the taproot output.

(I think I could come with some use-case from lex mercatoria where if you
play out a hardship provision you want to tweak all the other provisions by
a CSV delay while conserving the rest of their policy)

> And second, it doesn't provide a way for utxos to "interact", which is
> something that is interesting for automated market makers [5], but perhap=
s
> only interesting for chains aiming to support multiple asset types,
> and not bitcoin directly. On the other hand, perhaps combining it with
> CTV might be enough to solve that, particularly if the hash passed to
> CTV is constructed via script/CAT/etc.

That's where SIGHASH_GROUP might be more interesting as you could generate
transaction "puzzles".

IIUC, the problem is how to have a set of ratios between x/f(x). I think it
can be simplified to just generate pairs of input btc-amount/output
usdt-amount for the whole range of strike price you want to cover.

Each transaction puzzle has 1-input/2-outputs. The first output is signed
with SIGHASH_ANYPUBKEY but committed to a USDT amount. The second output is
signed with SIGHASH_ANYAMOUNT but committed to the maker pubkey. The input
commits to the spent BTC amount but not the spent txid/scriptPubKey.
The maker generates a Taproot tree where each leaf is committing to a
different "strike price".

A taker is finalizing the puzzle by inserting its withdraw scriptPubKey for
the first output and the maker amount for the second output. The
transitivity value output group rule guarantees that a malicious taker
can't siphon the fund.

> (I think everything described here could be simulated with CAT and
> CHECKSIGFROMSTACK (and 64bit maths operators and some way to access
> the internal public key), the point of introducing dedicated opcodes
> for this functionality rather than (just) having more generic opcodes
> would be to make the feature easy to use correctly, and, presuming it
> actually has a wide set of use cases, to make it cheap and efficient
> both to use in wallets, and for nodes to validate)

Yeah, I think CHECKSIGFROMSTACK is a no-go if we want to emulate
TAPLEAF_UPDATE_VERIFY functionality. If you want to update the 100th
tapscript, I believe we'll have to throw on the stack the corresponding
merkle branch and it sounds inefficient in terms of witness space ? Though
ofc, in both cases we bear the tree traversal computational cost ?

Really really excited to see progress on more powerful covenants for
Bitcoin :)

Cheers,
Antoine

[0] For the ideas genealogy, I think Greg's OP_MERKLE_UPDATE has been
circulating for a while and we chatted with Jeremy last year about the
current limitation of the script interpreter w.r.t expressing the factorial
complexity of advanced off-chain systems. I also remember Matt's artistic
drawing of a TAPLEAF_UPDATE_VERIFY ancestor on a Chaincode whiteboard :)

[1]
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-June/017964.ht=
ml

[2]
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-July/016249.ht=
ml

[3] A legal construction well-spread in the real-world. Known as
"indivision" in civil law".

[4] https://github.com/ariard/talk-slides/blob/master/advanced-contracts.pd=
f

Le jeu. 9 sept. 2021 =C3=A0 02:42, Anthony Towns via bitcoin-dev <
bitcoin-dev@lists.linuxfoundation.org> a =C3=A9crit :

> Hello world,
>
> A couple of years ago I had a flight of fancy [0] imagining how it
> might be possible for everyone on the planet to use bitcoin in a
> mostly decentralised/untrusted way, without requiring a block size
> increase. It was a bit ridiculous and probably doesn't quite hold up,
> and beyond needing all the existing proposals to be implemented (taproot,
> ANYPREVOUT, CTV, eltoo, channel factories), it also needed a covenant
> opcode [1]. I came up with something that I thought fit well with taproot=
,
> but couldn't quite figure out how to use it for anything other than my
> ridiculous scheme, so left it at that.
>
> But recently [2] Greg Maxwell emailed me about his own cool idea for a
> covenant opcode, which turned out to basically be a reinvention of the
> same idea but with more functionality, a better name and a less fanciful
> use case; and with that inspiration, I think I've also now figured out
> how to use it for a basic vault, so it seems worth making the idea a
> bit more public.
>
> I'll split this into two emails, this one's the handwavy overview,
> the followup will go into some of the implementation complexities.
>
>
>
> The basic idea is to think about "updating" a utxo by changing the
> taproot tree.
>
> As you might recall, a taproot address is made up from an internal public
> key (P) and a merkle tree of scripts (S) combined via the formula Q=3DP+H=
(P,
> S)*G to calculate the scriptPubKey (Q). When spending using a script,
> you provide the path to the merkle leaf that has the script you want
> to use in the control block. The BIP has an example [3] with 5 scripts
> arranged as ((A,B), ((C,D), E)), so if you were spending with E, you'd
> reveal a path of two hashes, one for (AB), then one for (CD), then you'd
> reveal your script E and satisfy it.
>
> So that makes it relatively easy to imagine creating a new taproot addres=
s
> based on the input you're spending by doing some or all of the following:
>
>  * Updating the internal public key (ie from P to P' =3D P + X)
>  * Trimming the merkle path (eg, removing CD)
>  * Removing the script you're currently executing (ie E)
>  * Adding a new step to the end of the merkle path (eg F)
>
> Once you've done those things, you can then calculate the new merkle
> root by resolving the updated merkle path (eg, S' =3D MerkleRootFor(AB,
> F, H_TapLeaf(E))), and then calculate a new scriptPubKey based on that
> and the updated internal public key (Q' =3D P' + H(P', S')).
>
> So the idea is to do just that via a new opcode "TAPLEAF_UPDATE_VERIFY"
> (TLUV) that takes three inputs: one that specifies how to update the
> internal public key (X), one that specifies a new step for the merkle pat=
h
> (F), and one that specifies whether to remove the current script and/or
> how many merkle path steps to remove. The opcode then calculates the
> scriptPubKey that matches that, and verifies that the output correspondin=
g
> to the current input spends to that scriptPubKey.
>
> That's useless without some way of verifying that the new utxo retains
> the bitcoin that was in the old utxo, so also include a new opcode
> IN_OUT_AMOUNT that pushes two items onto the stack: the amount from this
> input's utxo, and the amount in the corresponding output, and then expect
> anyone using TLUV to use maths operators to verify that funds are being
> appropriately retained in the updated scriptPubKey.
>
>
>
> Here's two examples of how you might use this functionality.
>
> First, a basic vault. The idea is that funds are ultimately protected
> by a cold wallet key (COLD) that's inconvenient to access but is as
> safe from theft as possible. In order to make day to day transactions
> more convenient, a hot wallet key (HOT) is also available, which is
> more vulnerable to theft. The vault design thus limits the hot wallet
> to withdrawing at most L satoshis every D blocks, so that if funds are
> stolen, you lose at most L, and have D blocks to use your cold wallet
> key to re-secure the funds and prevent further losses.
>
> To set this up with TLUV, you construct a taproot output with COLD as
> the internal public key, and a script that specifies:
>
>  * The tx is signed via HOT
>  * <D> CSV -- there's a relative time lock since the last spend
>  * If the input amount is less than L + dust threshold, fine, all done,
>    the vault can be emptied.
>  * Otherwise, the output amount must be at least (the input amount -
>    L), and do a TLUV check that the resulting sPK is unchanged
>
> So you can spend up to "L" satoshis via the hot wallet as long as you
> wait D blocks since the last spend, and can do whatever you want via a
> key path spend with the cold wallet.
>
> You could extend this to have a two phase protocol for spending, where
> first you use the hot wallet to say "in D blocks, allow spending up to
> L satoshis", and only after that can you use the hot wallet to actually
> spend funds. In that case supply a taproot sPK with COLD as the internal
> public key and two scripts, the "release" script, which specifies:
>
>  * The tx is signed via HOT
>  * Output amount is greater or equal to the input amount.
>  * Use TLUV to check:
>    + the output sPK has the same internal public key (ie COLD)
>    + the merkle path has one element trimmed
>    + the current script is included
>    + a new step is added that matches either H_LOCKED or H_AVAILABLE as
>      described below (depending on whether 0 or 1 was provided as
>      witness info)
>
> The other script is either "locked" (which is just "OP_RETURN") or
> "available" which specifies:
>
>  * The tx is signed via HOT
>  * <D> CSV -- there's a relative time lock since the last spend (ie,
>    when the "release" script above was used)
>  * If the input amount is less than L, fine, all done, the vault can
>    be emptied
>  * Otherwise, the output amount must be at least (the input amount minus
>    L), and via TLUV, check the resulting sPK keeps the internal pubkey
>    unchanged, keeps the merkle path, drops the current script, and adds
>    H_LOCKED as the new step.
>
> H_LOCKED and H_AVAILABLE are just the TapLeaf hash corresponding to the
> "locked" and "available" scripts.
>
> I believe this latter setup matches the design Bryan Bishop talked about
> a couple of years ago [4], with the benefit that it's fully recursive,
> allows withdrawals to vary rather than be the fixed amount L (due to not
> relying on pre-signed transactions), and generally seems a bit simpler
> to work with.
>
>
>
> The second scheme is allowing for a utxo to represent a group's pooled
> funds. The idea being that as long as everyone's around you can use
> the taproot key path to efficiently move money around within the pool,
> or use a single transaction and signature for many people in the pool
> to make payments. But key path spends only work if everyone's available
> to sign -- what happens if someone disappears, or loses access to their
> keys, or similar? For that, we want to have script paths to allow other
> people to reclaim their funds even if everyone else disappears. So we
> setup scripts for each participant, eg for Alice:
>
>  * The tx is signed by Alice
>  * The output value must be at least the input value minus Alice's balanc=
e
>  * Must pass TLUV such that:
>    + the internal public key is the old internal pubkey minus Alice's key
>    + the currently executing script is dropped from the merkle path
>    + no steps are otherwise removed or added
>
> The neat part here is that if you have many participants in the pool,
> the pool continues to operate normally even if someone makes use of the
> escape hatch -- the remaining participants can still use the key path to
> spend efficiently, and they can each unilaterally withdraw their balance
> via their own script path. If everyone decides to exit, whoever is last
> can spend the remaining balance directly via the key path.
>
> Compared to having on-chain transactions using non-pooled funds, this
> is more efficient and private: a single one-in, one-out transaction
> suffices for any number of transfers within the pool, and there's no
> on-chain information about who was sending/receiving the transfers, or
> how large the transfers were; and for transfers out of the pool, there's
> no on-chain indication which member of the pool is sending the funds,
> and multiple members of the pool can send funds to multiple destinations
> with only a single signature. The major constraint is that you need
> everyone in the pool to be online in order to sign via the key path,
> which provides a practical limit to how many people can reasonably be
> included in a pool before there's a breakdown.
>
> Compared to lightning (eg eltoo channel factories with multiple
> participants), the drawback is that no transfer is final without an
> updated state being committed on chain, however there are also benefits
> including that if one member of the pool unilaterally exits, that
> doesn't reveal the state of anyone remaining in the pool (eg an eltoo
> factory would likely reveal the balances of everyone else's channels at
> that point).
>
> A simpler case for something like this might be for funding a joint
> venture -- suppose you're joining with some other early bitcoiners to
> buy land to build a citadel, so you each put 20 BTC into a pooled utxo,
> ready to finalise the land purchase in a few months, but you also want
> to make sure you can reclaim the funds if the deal falls through. So
> you might include scripts like the above that allow you to reclaim your
> balance, but add a CLTV condition preventing anyone from doing that until
> the deal's deadline has passed. If the deal goes ahead, you all transfer
> the funds to the vendor via the keypath; if it doesn't work out, you
> hopefully return your funds via the keypath, but if things turn really
> sour, you can still just directly reclaim your 20 BTC yourself via the
> script path.
>
>
>
> I think a nice thing about this particular approach to recursive covenant=
s
> at a conceptual level is that it automatically leaves the key path as an
> escape mechanism -- rather than having to build a base case manually,
> and have the risk that it might not work because of some bug, locking
> your funds into the covenant permanently; the escape path is free, easy,
> and also the optimal way of spending things when everything is working
> right. (Of course, you could set the internal public key to a NUMS point
> and shoot yourself in the foot that way anyway)
>
>
>
> I think there's two limitations of this method that are worth pointing ou=
t.
>
> First it can't tweak scripts in areas of the merkle tree that it can't
> see -- I don't see a way of doing that particularly efficiently, so maybe
> it's best just to leave that as something for the people responsible for
> the funds to negotiate via the keypath, in which case it's automatically
> both private and efficient since all the details stay off-chain, anyway
>
> And second, it doesn't provide a way for utxos to "interact", which is
> something that is interesting for automated market makers [5], but perhap=
s
> only interesting for chains aiming to support multiple asset types,
> and not bitcoin directly. On the other hand, perhaps combining it with
> CTV might be enough to solve that, particularly if the hash passed to
> CTV is constructed via script/CAT/etc.
>
>
>
> (I think everything described here could be simulated with CAT and
> CHECKSIGFROMSTACK (and 64bit maths operators and some way to access
> the internal public key), the point of introducing dedicated opcodes
> for this functionality rather than (just) having more generic opcodes
> would be to make the feature easy to use correctly, and, presuming it
> actually has a wide set of use cases, to make it cheap and efficient
> both to use in wallets, and for nodes to validate)
>
> Cheers,
> aj
>
> [0] https://gist.github.com/ajtowns/dc9a59cf0a200bd1f9e6fb569f76f7a0
>
> [1] Roughly, the idea was that if you have ~9 billion people using
>     bitcoin, but can only have ~1000 transactions per block, then you
>     need have each utxo represent a significant number of people. That
>     means that you need a way of allowing the utxo's to be efficiently
>     spent, but need to introduce some level of trust since expecting
>     many people to constantly be online seems unreliable, but to remain
>     mostly decentralised/untrusted, you want to have some way of limiting
>     how much trust you're introducing, and that's where covenants come in=
.
>
> [2] Recently in covid-adjusted terms, or on the bitcoin consensus
>     change scale anyway...
>     https://mobile.twitter.com/ajtowns/status/1385091604357124100
>
> [3]
> https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#Constructi=
ng_and_spending_Taproot_outputs
>
> [4]
> https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-August/01723=
1.html
>
> [5] The idea behind an automated market maker being that you setup a
>     script that says "you can withdraw x BTC if you deposit f(x) units of
>     USDT, or you can withdraw g(x) units of USDT if you deposit x units
>     of BTC", with f(x)/x giving the buy price, and f(x)>g(x) meaning
>     you make a profit. Being able to specify a covenant that links the
>     change in value to the BTC utxo (+/-x) and the change in value to
>     the USDT utxo (+f(x) or -g(x)) is what you'd need to support this
>     sort of use case, but TLUV doesn't provide a way to do that linkage.
>
> _______________________________________________
> bitcoin-dev mailing list
> bitcoin-dev@lists.linuxfoundation.org
> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
>

--00000000000051308105cb9c5339
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr">Hi AJ,<br><br>Thanks for finally putting the pieces togeth=
er! [0]<br>=C2=A0<br>We&#39;ve been hacking with Gleb on a paper for the Co=
inPool protocol [1] during the last weeks and it should be public soon, hop=
efully highlighting what kind of scheme, TAPLEAF_UPDATE_VERIFY-style of cov=
enant enable :)<br><br>Here few early feedbacks on this specific proposal,<=
br><br>&gt; So that makes it relatively easy to imagine creating a new tapr=
oot address<br>&gt; based on the input you&#39;re spending by doing some or=
 all of the following:<br>&gt; <br>&gt; =C2=A0* Updating the internal publi=
c key (ie from P to P&#39; =3D P + X)<br>&gt; =C2=A0* Trimming the merkle p=
ath (eg, removing CD)<br>&gt; =C2=A0* Removing the script you&#39;re curren=
tly executing (ie E)<br>&gt; =C2=A0* Adding a new step to the end of the me=
rkle path (eg F)<br><br>&quot;Talk is cheap. Show me the code&quot; :p<br><=
br>=C2=A0 =C2=A0 case OP_MERKLESUB:<br>=C2=A0 =C2=A0 {<br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 if (!(flags &amp; SCRIPT_VERIFY_MERKLESUB)) {<br>=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 break;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 }<br><br>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (stack.size() &lt; 2) {<br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 return set_error(serror, SCRIPT_ERR_INVALID_STACK_=
OPERATION);<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 }<br><br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 valtype&amp; vchPubKey =3D stacktop(-1);<br><br>=C2=A0 =C2=A0 =C2=A0=
 =C2=A0 if (vchPubKey.size() !=3D 32) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 break;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 }<br><br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 const std::vector&lt;unsigned char&gt;&amp; vch =3D stacktop(-2)=
;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 int nOutputPos =3D CScriptNum(stacktop(-2)=
, fRequireMinimal).getint();<br><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (nOutput=
Pos &lt; 0) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return set_error=
(serror, SCRIPT_ERR_NEGATIVE_MERKLEVOUT);<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 }<=
br><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!checker.CheckMerkleUpdate(*execdata=
.m_control, nOutputPos, vchPubKey)) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=
 =C2=A0 return set_error(serror, SCRIPT_ERR_UNSATISFIED_MERKLESUB);<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 }<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 break;<br>=C2=A0 =
=C2=A0 }<br><br>=C2=A0 =C2=A0 case OP_NOP1: case OP_NOP5:<br><br><br><br>=
=C2=A0 =C2=A0 template &lt;class T&gt;<br>=C2=A0 =C2=A0 bool GenericTransac=
tionSignatureChecker&lt;T&gt;::CheckMerkleUpdate(const std::vector&lt;unsig=
ned char&gt;&amp; control, unsigned int out_pos, const std::vector&lt;unsig=
ned char&gt;&amp; point) const<br>=C2=A0 =C2=A0 {<br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 //! The internal pubkey (x-only, so no Y coordinate parity).<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 XOnlyPubKey p{uint256(std::vector&lt;unsigned char=
&gt;(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};<b=
r>=C2=A0 =C2=A0 =C2=A0 =C2=A0 //! Update the internal key by subtracting th=
e point.<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 XOnlyPubKey s{uint256(point)};<br>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 XOnlyPubKey u;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 t=
ry {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 u =3D p.UpdateInternalKey=
(s).value();<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 } catch (const std::bad_optiona=
l_access&amp; e) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return fals=
e;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 }<br>=C2=A0 =C2=A0 <br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 //! The first control node is made the new tapleaf hash.<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 //! TODO: what if there is no control node ?<br>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 uint256 updated_tapleaf_hash;<br>=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 updated_tapleaf_hash =3D uint256(std::vector&lt;unsigned char=
&gt;(control.data() + TAPROOT_CONTROL_BASE_SIZE, control.data() + TAPROOT_C=
ONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE));<br>=C2=A0 =C2=A0 <br>=C2=A0=
 =C2=A0 =C2=A0 =C2=A0 //! The committed-to output must be in the spent tran=
saction vout range.<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (out_pos &gt;=3D txTo=
-&gt;vout.size()) return false;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 int witnessv=
ersion;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 std::vector&lt;unsigned char&gt; wit=
nessprogram;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 txTo-&gt;vout[out_pos].scriptPu=
bKey.IsWitnessProgram(witnessversion, witnessprogram);<br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 //! The committed to output must be a witness v1 program at leas=
t<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (witnessversion =3D=3D 0) {<br>=C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return false;<br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 } else if (witnessversion =3D=3D 1) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0=
 =C2=A0 =C2=A0 //! The committed-to output.<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 const XOnlyPubKey q{uint256(witnessprogram)};<br>=C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 //! Compute the Merkle root from the leaf a=
nd the incremented by one path.<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 const uint256 merkle_root =3D ComputeTaprootMerkleRoot(control, updated=
_tapleaf_hash, 1);<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 //! TODO mo=
dify MERKLESUB design<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 bool par=
ity_ret =3D q.CheckTapTweak(u, merkle_root, true);<br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 bool no_parity_ret =3D q.CheckTapTweak(u, merkle_root,=
 false);<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!parity_ret &amp;=
&amp; !no_parity_ret) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=
 =C2=A0 return false;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 }<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 return true;<br>=
=C2=A0 =C2=A0 }<br><br><br>Here the main chunks for an &quot;&lt;n&gt; &lt;=
point&gt; OP_MERKLESUB&quot; opcode, with `n` the output position which is =
checked for update and `point` the x-only pubkey which must be subtracted f=
rom the internal key.<br><br>I think one design advantage of explicitly pas=
sing the output position as a stack element is giving more flexibility to y=
our contract dev. The first output could be SIGHASH_ALL locked-down. e.g &q=
uot;you have to pay Alice on output 1 while pursuing the contract semantic =
on output 2&quot;.<br><br>One could also imagine a list of output positions=
 to force the taproot update on multiple outputs (&quot;OP_MULTIMERKLESUB&q=
uot;). Taking back your citadel joint venture example, partners could decid=
e to split the funds in 3 equivalent amounts *while* conserving the pre-neg=
otiated script policies [2]<br><br>For the merkle branches extension, I was=
 thinking of introducing a separate OP_MERKLEADD, maybe to *add* a point to=
 the internal pubkey group signer. If you&#39;re only interested in leaf pr=
uning, using OP_MERKLESUB only should save you one byte of empty vector ?<b=
r><br>We can also explore more fancy opcodes where the updated merkle branc=
h is pushed on the stack for deep manipulations. Or even n-dimensions inspe=
ctions if combined with your G&#39;root [3] ?<br><br>Note, this current OP_=
MERKLESUB proposal doesn&#39;t deal with committing the parity of the inter=
nal pubkey as part of the spent utxo. As you highlighted well in your other=
 mail, if we want to conserve the updated key-path across a sequence of TLU=
V-covenanted transactions, we need either<br>a) to select a set of initial =
points, where whatever combination of add/sub, it yields an even-y point. O=
r b) have the even/odd bit re-committed at each update. Otherwise, we&#39;r=
e not guaranteed to cancel the point from the aggregated key.<br><br>This p=
roperty is important for CoinPool. Let&#39;s say you have A+B+C+D, after th=
e D withdraw transaction has been confirmed on-chain, you want A+B+C to ret=
ain the ability to use the key-path and update the off-chain state, without=
 forcing a script path spend to a new setup.<br><br>If we put the updated i=
nternal key parity bit in the first control byte, we need to have a=C2=A0 r=
edundant commitment somewhere else as we can&#39;t trust the spender to not=
 be willingly to break the key-path spend of the remaining group of signers=
. <br><br>One solution I was thinking about was introducing a new tapscript=
 version (`TAPROOT_INTERNAL_TAPSCRIPT`) signaling that VerifyTaprootCommitm=
ent must compute the TapTweak with a new TapTweak=3D(internal_pubkey || mer=
kle_root || parity_bit). A malicious participant wouldn&#39;t be able to in=
terfere with the updated internal key as it would break its own spending ta=
proot commitment verification ?<br><br>&gt; That&#39;s useless without some=
 way of verifying that the new utxo retains<br>&gt; the bitcoin that was in=
 the old utxo, so also include a new opcode<br>&gt; IN_OUT_AMOUNT that push=
es two items onto the stack: the amount from this<br>&gt; input&#39;s utxo,=
 and the amount in the corresponding output, and then expect<br>&gt; anyone=
 using TLUV to use maths operators to verify that funds are being<br>&gt; a=
ppropriately retained in the updated scriptPubKey.<br><br>Credit to you for=
 the SIGHASH_GROUP design, here the code, with SIGHASH_ANYPUBKEY/ANYAMOUNT =
extensions.<br><br>=C2=A0 =C2=A0 if ((output_type &amp; SIGHASH_GROUP) =3D=
=3D SIGHASH_GROUP) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 // Verify the output gr=
oup bounds<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (execdata.m_bundle-&gt;first =
=3D=3D execdata.m_bundle-&gt;second || execdata.m_bundle-&gt;second &gt;=3D=
 tx_to.vout.size()) return false;<br><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 // Ver=
ify the value commitment<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (VerifyOutputsGr=
oup(tx_to, cache.m_spent_outputs[in_pos].nValue, execdata.m_bundle-&gt;firs=
t, execdata.m_bundle-&gt;second)) return false; =C2=A0 =C2=A0 =C2=A0 =C2=A0=
 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 <br><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 for (unsigned int out_pos =3D ex=
ecdata.m_bundle-&gt;first; out_pos &lt; execdata.m_bundle-&gt;second + 1; o=
ut_pos++) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 bool anypubkey_fla=
g =3D false;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 bool anyamount_fl=
ag =3D false;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 std::map&lt;unsi=
gned int, char&gt;::const_iterator it;<br><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 if ((output_type &amp; SIGHASH_GROUP_ANYPUBKEY) =3D=3D SIGHAS=
H_GROUP_ANYPUBKEY) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 it =3D execdata.m_anypubkeys.find(out_pos);<br>=C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (it !=3D execdata.m_anypubkeys.end() =
&amp;&amp; it-&gt;second =3D=3D 1) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 anypubkey_flag =3D true;<br>=C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }<br>=C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 }<br><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if ((o=
utput_type &amp; SIGHASH_GROUP_ANYAMOUNT) =3D=3D SIGHASH_GROUP_ANYAMOUNT) {=
<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 it =3D execdata=
.m_anyamounts.find(out_pos);<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 if (it !=3D execdata.m_anyamounts.end() &amp;&amp; it-&gt;sec=
ond =3D=3D 1) {<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 anyamount_flag =3D true;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 }<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }<b=
r><br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!anypubkey_flag) {<br>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ss &lt;&lt; tx_to.v=
out[out_pos].scriptPubKey;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }<b=
r>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!anyamount_flag) {<br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ss &lt;&lt; tx_to.vout=
[out_pos].nValue;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }<br><br>=C2=
=A0 =C2=A0 =C2=A0 =C2=A0 }<br>=C2=A0 =C2=A0 }<br><br>I think it&#39;s achie=
ving the same effect as IN_OUT_AMOUNT, at least for CoinPool use-case. A Mu=
Sig=C2=A0 `contract_pubkey` can commit to the `to_withdraw` output while al=
lowing a wildcard for the `to_pool` output nValue/scriptPubKey. The nValue =
correctness will be ensured by the group-value-lock validation rule (`Verif=
yOutputsGroup`) and scriptPubkey by OP_MERKLESUB commitment.<br><br>I think=
 witness data size it&#39;s roughly equivalent as the annex fields must be =
occupied by the output group commitment. SIGHASH_GROUP might be more flexib=
le than IN_OUT_AMOUNT for a range of use-cases, see my point on AMM.<br><br=
>&gt; The second scheme is allowing for a utxo to represent a group&#39;s p=
ooled<br>&gt; funds. The idea being that as long as everyone&#39;s around y=
ou can use<br>&gt; the taproot key path to efficiently move money around wi=
thin the pool,<br>&gt; or use a single transaction and signature for many p=
eople in the pool<br>&gt; to make payments. But key path spends only work i=
f everyone&#39;s available<br>&gt; to sign -- what happens if someone disap=
pears, or loses access to their<br>&gt; keys, or similar? For that, we want=
 to have script paths to allow other<br>&gt; people to reclaim their funds =
even if everyone else disappears. So we<br>&gt; setup scripts for each part=
icipant, eg for Alice:<br>&gt; <br>&gt; =C2=A0* The tx is signed by Alice<b=
r>&gt; =C2=A0* The output value must be at least the input value minus Alic=
e&#39;s balance<br>&gt; =C2=A0* Must pass TLUV such that:<br>&gt; =C2=A0 =
=C2=A0+ the internal public key is the old internal pubkey minus Alice&#39;=
s key<br>&gt; =C2=A0 =C2=A0+ the currently executing script is dropped from=
 the merkle path<br>&gt; =C2=A0 =C2=A0+ no steps are otherwise removed or a=
dded<br><br>Yes the security model is roughly similar to the LN one. Instea=
d of a counter-signed commitment transaction which can be broadcast at any =
point during channel lifetime, you have a pre-signed withdraw transaction s=
ending to {`to_withdraw`,`to_pool`} outputs. Former is your off-chain balan=
ce, the latter one is the pool balance, and one grieved with the updated Ta=
proot output. The withdraw tapscript force the point subtraction with the f=
ollowing format (`&lt;n&gt; &lt;withdraw_point&gt; &lt;OP_MERKLESUB&gt; &lt=
;33-byte contract_pubkey&gt; OP_CHECKSIG)<br><br>&gt; A simpler case for so=
mething like this might be for funding a joint<br>&gt; venture -- suppose y=
ou&#39;re joining with some other early bitcoiners to<br>&gt; buy land to b=
uild a citadel, so you each put 20 BTC into a pooled utxo,<br>&gt; ready to=
 finalise the land purchase in a few months, but you also want<br>&gt; to m=
ake sure you can reclaim the funds if the deal falls through. So<br>&gt; yo=
u might include scripts like the above that allow you to reclaim your<br>&g=
t; balance, but add a CLTV condition preventing anyone from doing that unti=
l<br>&gt; the deal&#39;s deadline has passed. If the deal goes ahead, you a=
ll transfer<br>&gt; the funds to the vendor via the keypath; if it doesn&#3=
9;t work out, you<br>&gt; hopefully return your funds via the keypath, but =
if things turn really<br>&gt; sour, you can still just directly reclaim you=
r 20 BTC yourself via the<br>&gt; script path.<br><br>Yes, that kind of blo=
ckchain validation semantic extension is vaudoo-magic if we want to enable =
smart corporation/scalable multi-event contracts. I gave a presentation on =
advanced bitcoin contracts two years ago, mentioning we would need covenant=
s to solve the factorial complexity on edge-case [4]<br><br>Bitcoin ledger =
would fit perfectly well to host international commerce law style of contra=
cts, where you have a lot of usual fancy provisions (e.g hardship, delay pe=
nalty, ...) :)<br><br>&gt; First it can&#39;t tweak scripts in areas of the=
 merkle tree that it can&#39;t<br>&gt; see -- I don&#39;t see a way of doin=
g that particularly efficiently, so maybe<br>&gt; it&#39;s best just to lea=
ve that as something for the people responsible for<br>&gt; the funds to ne=
gotiate via the keypath, in which case it&#39;s automatically<br>&gt; both =
private and efficient since all the details stay off-chain, anyway<br><br>Y=
eah, in that kind of case, we might want to push the merkle root as a stack=
 element but still update the internal pubkey from the spent utxo ? This ne=
w merkle_root would be the tree of tweaked scripts as you expect them if yo=
u execute *this* tapscript. And you can still this new tree with a tapbranc=
h inherited from the taproot output.<br><br>(I think I could come with some=
 use-case from lex mercatoria where if you play out a hardship provision yo=
u want to tweak all the other provisions by a CSV delay while conserving th=
e rest of their policy)<br><br>&gt; And second, it doesn&#39;t provide a wa=
y for utxos to &quot;interact&quot;, which is<br>&gt; something that is int=
eresting for automated market makers [5], but perhaps<br>&gt; only interest=
ing for chains aiming to support multiple asset types,<br>&gt; and not bitc=
oin directly. On the other hand, perhaps combining it with<br>&gt; CTV migh=
t be enough to solve that, particularly if the hash passed to<br>&gt; CTV i=
s constructed via script/CAT/etc.<br><br>That&#39;s where SIGHASH_GROUP mig=
ht be more interesting as you could generate transaction &quot;puzzles&quot=
;.<br><br>IIUC, the problem is how to have a set of ratios between x/f(x). =
I think it can be simplified to just generate pairs of input btc-amount/out=
put usdt-amount for the whole range of strike price you want to cover.<br><=
br>Each transaction puzzle has 1-input/2-outputs. The first output is signe=
d with SIGHASH_ANYPUBKEY but committed to a USDT amount. The second output =
is signed with SIGHASH_ANYAMOUNT but committed to the maker pubkey. The inp=
ut commits to the spent BTC amount but not the spent txid/scriptPubKey. <br=
>The maker generates a Taproot tree where each leaf is committing to a diff=
erent &quot;strike price&quot;.<br><br>A taker is finalizing the puzzle by =
inserting its withdraw scriptPubKey for the first output and the maker amou=
nt for the second output. The transitivity value output group rule guarante=
es that a malicious taker can&#39;t siphon the fund.<br><br>&gt; (I think e=
verything described here could be simulated with CAT and<br>&gt; CHECKSIGFR=
OMSTACK (and 64bit maths operators and some way to access<br>&gt; the inter=
nal public key), the point of introducing dedicated opcodes<br>&gt; for thi=
s functionality rather than (just) having more generic opcodes<br>&gt; woul=
d be to make the feature easy to use correctly, and, presuming it<br>&gt; a=
ctually has a wide set of use cases, to make it cheap and efficient<br>&gt;=
 both to use in wallets, and for nodes to validate)<br><br>Yeah, I think CH=
ECKSIGFROMSTACK is a no-go if we want to emulate TAPLEAF_UPDATE_VERIFY func=
tionality. If you want to update the 100th tapscript, I believe we&#39;ll h=
ave to throw on the stack the corresponding merkle branch and it sounds ine=
fficient in terms of witness space ? Though ofc, in both cases we bear the =
tree traversal computational cost ?<br><br>Really really excited to see pro=
gress on more powerful covenants for Bitcoin :)<br><br>Cheers,<br>Antoine<b=
r><br>[0] For the ideas genealogy, I think Greg&#39;s OP_MERKLE_UPDATE has =
been circulating for a while and we chatted with Jeremy last year about the=
 current limitation of the script interpreter w.r.t expressing the factoria=
l complexity of advanced off-chain systems. I also remember Matt&#39;s arti=
stic drawing of a TAPLEAF_UPDATE_VERIFY ancestor on a Chaincode whiteboard =
:)<br><br>[1] <a href=3D"https://lists.linuxfoundation.org/pipermail/bitcoi=
n-dev/2020-June/017964.html">https://lists.linuxfoundation.org/pipermail/bi=
tcoin-dev/2020-June/017964.html</a><br><br>[2] <a href=3D"https://lists.lin=
uxfoundation.org/pipermail/bitcoin-dev/2018-July/016249.html">https://lists=
.linuxfoundation.org/pipermail/bitcoin-dev/2018-July/016249.html</a><br><br=
>[3] A legal construction well-spread in the real-world. Known as &quot;ind=
ivision&quot; in civil law&quot;.<br><br>[4] <a href=3D"https://github.com/=
ariard/talk-slides/blob/master/advanced-contracts.pdf">https://github.com/a=
riard/talk-slides/blob/master/advanced-contracts.pdf</a><br></div><br><div =
class=3D"gmail_quote"><div dir=3D"ltr" class=3D"gmail_attr">Le=C2=A0jeu. 9 =
sept. 2021 =C3=A0=C2=A002:42, Anthony Towns via bitcoin-dev &lt;<a href=3D"=
mailto:bitcoin-dev@lists.linuxfoundation.org">bitcoin-dev@lists.linuxfounda=
tion.org</a>&gt; a =C3=A9crit=C2=A0:<br></div><blockquote class=3D"gmail_qu=
ote" style=3D"margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,20=
4);padding-left:1ex">Hello world,<br>
<br>
A couple of years ago I had a flight of fancy [0] imagining how it<br>
might be possible for everyone on the planet to use bitcoin in a<br>
mostly decentralised/untrusted way, without requiring a block size<br>
increase. It was a bit ridiculous and probably doesn&#39;t quite hold up,<b=
r>
and beyond needing all the existing proposals to be implemented (taproot,<b=
r>
ANYPREVOUT, CTV, eltoo, channel factories), it also needed a covenant<br>
opcode [1]. I came up with something that I thought fit well with taproot,<=
br>
but couldn&#39;t quite figure out how to use it for anything other than my<=
br>
ridiculous scheme, so left it at that.<br>
<br>
But recently [2] Greg Maxwell emailed me about his own cool idea for a<br>
covenant opcode, which turned out to basically be a reinvention of the<br>
same idea but with more functionality, a better name and a less fanciful<br=
>
use case; and with that inspiration, I think I&#39;ve also now figured out<=
br>
how to use it for a basic vault, so it seems worth making the idea a<br>
bit more public.<br>
<br>
I&#39;ll split this into two emails, this one&#39;s the handwavy overview,<=
br>
the followup will go into some of the implementation complexities.<br>
<br>
<br>
<br>
The basic idea is to think about &quot;updating&quot; a utxo by changing th=
e<br>
taproot tree.<br>
<br>
As you might recall, a taproot address is made up from an internal public<b=
r>
key (P) and a merkle tree of scripts (S) combined via the formula Q=3DP+H(P=
,<br>
S)*G to calculate the scriptPubKey (Q). When spending using a script,<br>
you provide the path to the merkle leaf that has the script you want<br>
to use in the control block. The BIP has an example [3] with 5 scripts<br>
arranged as ((A,B), ((C,D), E)), so if you were spending with E, you&#39;d<=
br>
reveal a path of two hashes, one for (AB), then one for (CD), then you&#39;=
d<br>
reveal your script E and satisfy it.<br>
<br>
So that makes it relatively easy to imagine creating a new taproot address<=
br>
based on the input you&#39;re spending by doing some or all of the followin=
g:<br>
<br>
=C2=A0* Updating the internal public key (ie from P to P&#39; =3D P + X)<br=
>
=C2=A0* Trimming the merkle path (eg, removing CD)<br>
=C2=A0* Removing the script you&#39;re currently executing (ie E)<br>
=C2=A0* Adding a new step to the end of the merkle path (eg F)<br>
<br>
Once you&#39;ve done those things, you can then calculate the new merkle<br=
>
root by resolving the updated merkle path (eg, S&#39; =3D MerkleRootFor(AB,=
<br>
F, H_TapLeaf(E))), and then calculate a new scriptPubKey based on that<br>
and the updated internal public key (Q&#39; =3D P&#39; + H(P&#39;, S&#39;))=
.<br>
<br>
So the idea is to do just that via a new opcode &quot;TAPLEAF_UPDATE_VERIFY=
&quot;<br>
(TLUV) that takes three inputs: one that specifies how to update the<br>
internal public key (X), one that specifies a new step for the merkle path<=
br>
(F), and one that specifies whether to remove the current script and/or<br>
how many merkle path steps to remove. The opcode then calculates the<br>
scriptPubKey that matches that, and verifies that the output corresponding<=
br>
to the current input spends to that scriptPubKey.<br>
<br>
That&#39;s useless without some way of verifying that the new utxo retains<=
br>
the bitcoin that was in the old utxo, so also include a new opcode<br>
IN_OUT_AMOUNT that pushes two items onto the stack: the amount from this<br=
>
input&#39;s utxo, and the amount in the corresponding output, and then expe=
ct<br>
anyone using TLUV to use maths operators to verify that funds are being<br>
appropriately retained in the updated scriptPubKey.<br>
<br>
<br>
<br>
Here&#39;s two examples of how you might use this functionality.<br>
<br>
First, a basic vault. The idea is that funds are ultimately protected<br>
by a cold wallet key (COLD) that&#39;s inconvenient to access but is as<br>
safe from theft as possible. In order to make day to day transactions<br>
more convenient, a hot wallet key (HOT) is also available, which is<br>
more vulnerable to theft. The vault design thus limits the hot wallet<br>
to withdrawing at most L satoshis every D blocks, so that if funds are<br>
stolen, you lose at most L, and have D blocks to use your cold wallet<br>
key to re-secure the funds and prevent further losses.<br>
<br>
To set this up with TLUV, you construct a taproot output with COLD as<br>
the internal public key, and a script that specifies:<br>
<br>
=C2=A0* The tx is signed via HOT<br>
=C2=A0* &lt;D&gt; CSV -- there&#39;s a relative time lock since the last sp=
end<br>
=C2=A0* If the input amount is less than L + dust threshold, fine, all done=
,<br>
=C2=A0 =C2=A0the vault can be emptied.<br>
=C2=A0* Otherwise, the output amount must be at least (the input amount -<b=
r>
=C2=A0 =C2=A0L), and do a TLUV check that the resulting sPK is unchanged<br=
>
<br>
So you can spend up to &quot;L&quot; satoshis via the hot wallet as long as=
 you<br>
wait D blocks since the last spend, and can do whatever you want via a<br>
key path spend with the cold wallet.<br>
<br>
You could extend this to have a two phase protocol for spending, where<br>
first you use the hot wallet to say &quot;in D blocks, allow spending up to=
<br>
L satoshis&quot;, and only after that can you use the hot wallet to actuall=
y<br>
spend funds. In that case supply a taproot sPK with COLD as the internal<br=
>
public key and two scripts, the &quot;release&quot; script, which specifies=
:<br>
<br>
=C2=A0* The tx is signed via HOT<br>
=C2=A0* Output amount is greater or equal to the input amount.<br>
=C2=A0* Use TLUV to check:<br>
=C2=A0 =C2=A0+ the output sPK has the same internal public key (ie COLD)<br=
>
=C2=A0 =C2=A0+ the merkle path has one element trimmed<br>
=C2=A0 =C2=A0+ the current script is included<br>
=C2=A0 =C2=A0+ a new step is added that matches either H_LOCKED or H_AVAILA=
BLE as<br>
=C2=A0 =C2=A0 =C2=A0described below (depending on whether 0 or 1 was provid=
ed as<br>
=C2=A0 =C2=A0 =C2=A0witness info)<br>
<br>
The other script is either &quot;locked&quot; (which is just &quot;OP_RETUR=
N&quot;) or<br>
&quot;available&quot; which specifies:<br>
<br>
=C2=A0* The tx is signed via HOT<br>
=C2=A0* &lt;D&gt; CSV -- there&#39;s a relative time lock since the last sp=
end (ie,<br>
=C2=A0 =C2=A0when the &quot;release&quot; script above was used)<br>
=C2=A0* If the input amount is less than L, fine, all done, the vault can<b=
r>
=C2=A0 =C2=A0be emptied<br>
=C2=A0* Otherwise, the output amount must be at least (the input amount min=
us<br>
=C2=A0 =C2=A0L), and via TLUV, check the resulting sPK keeps the internal p=
ubkey<br>
=C2=A0 =C2=A0unchanged, keeps the merkle path, drops the current script, an=
d adds<br>
=C2=A0 =C2=A0H_LOCKED as the new step.<br>
<br>
H_LOCKED and H_AVAILABLE are just the TapLeaf hash corresponding to the<br>
&quot;locked&quot; and &quot;available&quot; scripts.<br>
<br>
I believe this latter setup matches the design Bryan Bishop talked about<br=
>
a couple of years ago [4], with the benefit that it&#39;s fully recursive,<=
br>
allows withdrawals to vary rather than be the fixed amount L (due to not<br=
>
relying on pre-signed transactions), and generally seems a bit simpler<br>
to work with.<br>
<br>
<br>
<br>
The second scheme is allowing for a utxo to represent a group&#39;s pooled<=
br>
funds. The idea being that as long as everyone&#39;s around you can use<br>
the taproot key path to efficiently move money around within the pool,<br>
or use a single transaction and signature for many people in the pool<br>
to make payments. But key path spends only work if everyone&#39;s available=
<br>
to sign -- what happens if someone disappears, or loses access to their<br>
keys, or similar? For that, we want to have script paths to allow other<br>
people to reclaim their funds even if everyone else disappears. So we<br>
setup scripts for each participant, eg for Alice:<br>
<br>
=C2=A0* The tx is signed by Alice<br>
=C2=A0* The output value must be at least the input value minus Alice&#39;s=
 balance<br>
=C2=A0* Must pass TLUV such that:<br>
=C2=A0 =C2=A0+ the internal public key is the old internal pubkey minus Ali=
ce&#39;s key<br>
=C2=A0 =C2=A0+ the currently executing script is dropped from the merkle pa=
th<br>
=C2=A0 =C2=A0+ no steps are otherwise removed or added<br>
<br>
The neat part here is that if you have many participants in the pool,<br>
the pool continues to operate normally even if someone makes use of the<br>
escape hatch -- the remaining participants can still use the key path to<br=
>
spend efficiently, and they can each unilaterally withdraw their balance<br=
>
via their own script path. If everyone decides to exit, whoever is last<br>
can spend the remaining balance directly via the key path.<br>
<br>
Compared to having on-chain transactions using non-pooled funds, this<br>
is more efficient and private: a single one-in, one-out transaction<br>
suffices for any number of transfers within the pool, and there&#39;s no<br=
>
on-chain information about who was sending/receiving the transfers, or<br>
how large the transfers were; and for transfers out of the pool, there&#39;=
s<br>
no on-chain indication which member of the pool is sending the funds,<br>
and multiple members of the pool can send funds to multiple destinations<br=
>
with only a single signature. The major constraint is that you need<br>
everyone in the pool to be online in order to sign via the key path,<br>
which provides a practical limit to how many people can reasonably be<br>
included in a pool before there&#39;s a breakdown.<br>
<br>
Compared to lightning (eg eltoo channel factories with multiple<br>
participants), the drawback is that no transfer is final without an<br>
updated state being committed on chain, however there are also benefits<br>
including that if one member of the pool unilaterally exits, that<br>
doesn&#39;t reveal the state of anyone remaining in the pool (eg an eltoo<b=
r>
factory would likely reveal the balances of everyone else&#39;s channels at=
<br>
that point).<br>
<br>
A simpler case for something like this might be for funding a joint<br>
venture -- suppose you&#39;re joining with some other early bitcoiners to<b=
r>
buy land to build a citadel, so you each put 20 BTC into a pooled utxo,<br>
ready to finalise the land purchase in a few months, but you also want<br>
to make sure you can reclaim the funds if the deal falls through. So<br>
you might include scripts like the above that allow you to reclaim your<br>
balance, but add a CLTV condition preventing anyone from doing that until<b=
r>
the deal&#39;s deadline has passed. If the deal goes ahead, you all transfe=
r<br>
the funds to the vendor via the keypath; if it doesn&#39;t work out, you<br=
>
hopefully return your funds via the keypath, but if things turn really<br>
sour, you can still just directly reclaim your 20 BTC yourself via the<br>
script path.<br>
<br>
<br>
<br>
I think a nice thing about this particular approach to recursive covenants<=
br>
at a conceptual level is that it automatically leaves the key path as an<br=
>
escape mechanism -- rather than having to build a base case manually,<br>
and have the risk that it might not work because of some bug, locking<br>
your funds into the covenant permanently; the escape path is free, easy,<br=
>
and also the optimal way of spending things when everything is working<br>
right. (Of course, you could set the internal public key to a NUMS point<br=
>
and shoot yourself in the foot that way anyway)<br>
<br>
<br>
<br>
I think there&#39;s two limitations of this method that are worth pointing =
out.<br>
<br>
First it can&#39;t tweak scripts in areas of the merkle tree that it can&#3=
9;t<br>
see -- I don&#39;t see a way of doing that particularly efficiently, so may=
be<br>
it&#39;s best just to leave that as something for the people responsible fo=
r<br>
the funds to negotiate via the keypath, in which case it&#39;s automaticall=
y<br>
both private and efficient since all the details stay off-chain, anyway<br>
<br>
And second, it doesn&#39;t provide a way for utxos to &quot;interact&quot;,=
 which is<br>
something that is interesting for automated market makers [5], but perhaps<=
br>
only interesting for chains aiming to support multiple asset types,<br>
and not bitcoin directly. On the other hand, perhaps combining it with<br>
CTV might be enough to solve that, particularly if the hash passed to<br>
CTV is constructed via script/CAT/etc.<br>
<br>
<br>
<br>
(I think everything described here could be simulated with CAT and<br>
CHECKSIGFROMSTACK (and 64bit maths operators and some way to access<br>
the internal public key), the point of introducing dedicated opcodes<br>
for this functionality rather than (just) having more generic opcodes<br>
would be to make the feature easy to use correctly, and, presuming it<br>
actually has a wide set of use cases, to make it cheap and efficient<br>
both to use in wallets, and for nodes to validate)<br>
<br>
Cheers,<br>
aj<br>
<br>
[0] <a href=3D"https://gist.github.com/ajtowns/dc9a59cf0a200bd1f9e6fb569f76=
f7a0" rel=3D"noreferrer" target=3D"_blank">https://gist.github.com/ajtowns/=
dc9a59cf0a200bd1f9e6fb569f76f7a0</a><br>
<br>
[1] Roughly, the idea was that if you have ~9 billion people using<br>
=C2=A0 =C2=A0 bitcoin, but can only have ~1000 transactions per block, then=
 you<br>
=C2=A0 =C2=A0 need have each utxo represent a significant number of people.=
 That<br>
=C2=A0 =C2=A0 means that you need a way of allowing the utxo&#39;s to be ef=
ficiently<br>
=C2=A0 =C2=A0 spent, but need to introduce some level of trust since expect=
ing<br>
=C2=A0 =C2=A0 many people to constantly be online seems unreliable, but to =
remain<br>
=C2=A0 =C2=A0 mostly decentralised/untrusted, you want to have some way of =
limiting<br>
=C2=A0 =C2=A0 how much trust you&#39;re introducing, and that&#39;s where c=
ovenants come in.<br>
<br>
[2] Recently in covid-adjusted terms, or on the bitcoin consensus<br>
=C2=A0 =C2=A0 change scale anyway...<br>
=C2=A0 =C2=A0 <a href=3D"https://mobile.twitter.com/ajtowns/status/13850916=
04357124100" rel=3D"noreferrer" target=3D"_blank">https://mobile.twitter.co=
m/ajtowns/status/1385091604357124100</a> <br>
<br>
[3] <a href=3D"https://github.com/bitcoin/bips/blob/master/bip-0341.mediawi=
ki#Constructing_and_spending_Taproot_outputs" rel=3D"noreferrer" target=3D"=
_blank">https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#Cons=
tructing_and_spending_Taproot_outputs</a> <br>
<br>
[4] <a href=3D"https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019=
-August/017231.html" rel=3D"noreferrer" target=3D"_blank">https://lists.lin=
uxfoundation.org/pipermail/bitcoin-dev/2019-August/017231.html</a><br>
<br>
[5] The idea behind an automated market maker being that you setup a<br>
=C2=A0 =C2=A0 script that says &quot;you can withdraw x BTC if you deposit =
f(x) units of<br>
=C2=A0 =C2=A0 USDT, or you can withdraw g(x) units of USDT if you deposit x=
 units<br>
=C2=A0 =C2=A0 of BTC&quot;, with f(x)/x giving the buy price, and f(x)&gt;g=
(x) meaning<br>
=C2=A0 =C2=A0 you make a profit. Being able to specify a covenant that link=
s the<br>
=C2=A0 =C2=A0 change in value to the BTC utxo (+/-x) and the change in valu=
e to<br>
=C2=A0 =C2=A0 the USDT utxo (+f(x) or -g(x)) is what you&#39;d need to supp=
ort this<br>
=C2=A0 =C2=A0 sort of use case, but TLUV doesn&#39;t provide a way to do th=
at linkage.<br>
<br>
_______________________________________________<br>
bitcoin-dev mailing list<br>
<a href=3D"mailto:bitcoin-dev@lists.linuxfoundation.org" target=3D"_blank">=
bitcoin-dev@lists.linuxfoundation.org</a><br>
<a href=3D"https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev" =
rel=3D"noreferrer" target=3D"_blank">https://lists.linuxfoundation.org/mail=
man/listinfo/bitcoin-dev</a><br>
</blockquote></div>

--00000000000051308105cb9c5339--