Bởi Rusty Russell
Nguồn: https://rusty.ozlabs.org/2024/01/19/the-great-opcode-restoration.html
Ghi chú của Biên tập viên: Bài viết này là tổng quan về đề xuất "Khôi phục Tập lệnh Tuyệt vời" của tác giả Rusty Russell, được công bố rộng rãi vào tháng 1 năm 2024. Ý tưởng đằng sau "Khôi phục Tập lệnh" là khôi phục các mã lệnh Bitcoin Script đã bị vô hiệu hóa vào năm 2010 do sự cố DoS. Để tránh sự cố DoS tương tự khiến các mã lệnh này được kích hoạt lại, tác giả đề xuất một phương pháp mới để hạn chế việc sử dụng tài nguyên xác minh giao dịch và đề xuất các mã lệnh mới.
Tính đến tháng 9 năm 2025, tác giả đã viết bốn BIP liên quan đến các ý tưởng được đề cập trong bài viết này.
Trong vài bài viết gần đây, tôi đã suy ngẫm về những nâng cấp mà chúng ta có thể muốn thêm vào Bitcoin Script (ngôn ngữ lập trình Bitcoin ) sau khi chúng ta đã tự vấn. Script đã bị đình trệ ở phiên bản 0.3.1 do các cuộc tấn công từ chối dịch vụ: điều này luôn là một điều đáng tiếc, nhưng các tính năng như OP_TXHASH
đã làm rõ những hạn chế của Script.
Tập lệnh Bitcoin lỗi thời
Nhiều người biết Satoshi Nakamoto đã vô hiệu hóa OP_CAT
và một số mã lệnh khác trong phiên bản 0.3.1, nhưng Anthony Towns chỉ ra rằng trước phiên bản 0.3, phần mềm bitcoin cũng cho phép sử dụng kiểu BIGNUM của OpenSSL để triển khai các giá trị có độ dài tùy ý .
Đó là những ngày đầu của dự án Bitcoin, và tôi hoàn toàn hiểu mong muốn tránh các vấn đề DoS ngay lập tức và rõ ràng, cũng như khôi phục chức năng chỉ sau khi vấn đề đã được xem xét kỹ lưỡng. Đáng tiếc là phải mất nhiều năm sau (và hiện tại) mọi người mới hiểu được những khó khăn khi bổ sung chức năng vào Script.
Ngân sách mã lệnh có độ dài thay đổi: khôi phục hoàn toàn chức năng tập lệnh mà không gây ra DoS
BIP-342 thay thế giới hạn chữ ký toàn cầu bằng ngân sách hoạt động chữ ký dựa trên trọng số được thiết kế để hỗ trợ bất kỳ xác minh chữ ký hợp lý nào (chẳng hạn như các tập lệnh có thể tạo ra bằng miniscript) trong khi vẫn đủ lớn để tránh DoS.
Chúng ta có thể sử dụng phương pháp này cho các hoạt động khác, miễn là chi phí của các hoạt động đó liên quan đến kích thước toán hạng của chúng, và tương tự như vậy, loại bỏ các hạn chế tùy ý trong hệ thống kịch bản hiện có. Tôi gọi ý tưởng này là lập ngân sách "mã lệnh có độ dài thay đổi (varops)" vì nó áp dụng cho các hoạt động trên các đối tượng có độ dài thay đổi.
Bản dự thảo đề xuất của tôi thiết lập ngân sách mã lệnh có độ dài thay đổi để đơn giản:
- Trọng số giao dịch được nhân với 520.
Điều này đảm bảo rằng ngay cả khi ngân sách được áp dụng cho các tập lệnh hiện có, không có tập lệnh nào có thể không thể thực thi được (ví dụ: mọi OP_SHA256 luôn có thể hoạt động trên một phần tử ngăn xếp có độ dài tối đa và trọng số mã lệnh của riêng nó đủ để trang trải ngân sách).
Lưu ý: ngân sách này được áp dụng cho toàn bộ giao dịch, không phải cho mỗi đầu vào: điều này nhằm dự đoán các mã lệnh bộ nhớ, nghĩa là một tập lệnh rất ngắn có thể kiểm tra một đầu vào rất lớn.
Ngân sách tiêu thụ của mỗi mã lệnh như sau (các mã lệnh không được liệt kê sẽ không tiêu tốn ngân sách):
Mã lệnh | Tiêu thụ ngân sách mã lệnh có độ dài thay đổi |
---|---|
OP_MÈO | 0 |
OP_SUBSTR | 0 |
OP_LEFT | 0 |
OP_PHẢI | 0 |
OP_Đảo ngược | 1 + len(a) / 8 |
OP_AND | 1 + MAX(len(a), len(b)) / 8 |
OP_OR | 1 + MAX(len(a), len(b)) / 8 |
OP_XOR | 1 + MAX(len(a), len(b)) / 8 |
OP_2MUL | 1 + len(a) / 8 |
OP_2DIV | 1 + len(a) / 8 |
OP_ADD | 1 + MAX(len(a), len(b)) / 8 |
OP_SUB | 1 + MAX(len(a), len(b)) / 8 |
OP_MUL | (1 + len(a) / 8) * (1 + len(b) / 8 |
OP_DIV | (1 + len(a) / 8) * (1 + len(b) / 8 |
OP_MOD | (1 + len(a) / 8) * (1 + len(b) / 8 |
OP_LSHIFT | 1 + len(a) / 8 |
OP_RSHIFT | 1 + len(a) / 8 |
OP_EBAL | 1 + MAX(len(a), len(b)) / 8 |
OP_NOTEQUAL | 1 + MAX(len(a), len(b)) / 8 |
OP_SHA256 | 1 + len(a) |
OP_RIPEMD160 | 0 (không thành công nếu len(a) > 520 byte) |
OP_SHA1 | 0 (không thành công nếu len(a) > 520 byte) |
OP_HASH160 | 1 + len(a) |
OP_HASH256 | 1 + len(a) |
Xóa bỏ các hạn chế khác
Đề xuất khôi phục OP_CAT của Ethan Hilman vẫn giữ nguyên giới hạn 520 byte. Với ngân sách mã lệnh có độ dài thay đổi, giới hạn này có thể được loại bỏ và thay thế bằng giới hạn kích thước ngăn xếp tổng thể đã được áp dụng cho taproot v1 (1.000 phần tử và 520.000 byte).
Hơn nữa, nếu chúng ta muốn giới thiệu phiên bản SegWit mới (chẳng hạn như " gốc máy tổng quát " của Anthony Towns) hoặc muốn cho phép " nhập không cần chìa khóa", chúng ta có thể điều chỉnh các giới hạn này thành giới hạn trên hợp lý về kích thước khối (có thể là 10.000 phần tử hoặc tối đa 4M byte).
Thay đổi ngữ nghĩa một chút
Các giá trị sẽ vẫn là little-endian, nhưng không có dấu. Điều này giúp đơn giản hóa việc triển khai và làm cho tương tác giữa các phép toán bit và phép toán số học trở nên đơn giản hơn nhiều. Nó cho phép các số dương hiện có sử dụng các mã lệnh này mà không cần sửa đổi hoặc chuyển đổi.
Nếu chúng ta có ý định sử dụng phiên bản SegWit mới, các mã lệnh hiện có có thể được thay thế; nếu không, sẽ cần phải thêm các mã lệnh mới (ví dụ: OP_ADDV
).
Chi tiết triển khai
Phần mềm Bitcoin v0.3.0 sử dụng trình bao bọc lớp đơn giản xung quanh kiểu BIGNUM của OpenSSL, nhưng để đơn giản nhất, tôi đã triển khai lại mọi mã lệnh mà không cần phụ thuộc bên ngoài.
Ngoại trừ OP_EQUAL
/ OP_EQUALVERIFY
, mọi mã lệnh đều được chuyển đổi thành (hoặc từ) một vectơ little-endian của uint64_t
. Điều này có thể được tối ưu hóa bằng cách chuyển đổi theo yêu cầu.
OP_DIV
, OP_MOD
và OP_MUL
được triển khai thô sơ (so sánh với số lượng thao tác lớn của libgmp cho thấy phương pháp phức tạp hơn nhanh hơn nhiều).
Đánh giá chuẩn: Các giới hạn trên có đủ thấp để ngăn chặn DoS không?
Các giới hạn trên có đủ cao để có thể bỏ qua không?
Chúng ta có thể loại bỏ giới hạn 520 byte.
Nhưng chúng ta vẫn cần giới hạn kích thước tổng thể của ngăn xếp: khi sử dụng phiên bản SegWit mới, giới hạn này có thể tăng lên 400.000 byte; hoặc có thể giữ nguyên giới hạn hiện tại: 520.000 byte.
Trong bài viết trước " Xác định Khóa công khai của tập lệnh trong tập lệnh " ( bản dịch tiếng Trung ), tôi đã chỉ ra rằng đôi khi chúng ta muốn yêu cầu một loại điều kiện tập lệnh cụ thể, nhưng không phải là một tập lệnh chính xác: một ví dụ là điều khoản ràng buộc của loại hợp đồng an toàn, yêu cầu độ trễ nhưng không quan tâm đến việc có bất kỳ điều gì khác trong tập lệnh hay không.
Vấn đề là trong tập lệnh Taproot, bất kỳ mã lệnh nào không xác định ( OP_SUCCESSx
) sẽ khiến toàn bộ tập lệnh bị lỗi (không được thực thi), vì vậy chúng ta cần điều chỉnh điều này một chút. Đề xuất trước đây của tôi về một bộ phân cách khá rắc rối, vì vậy tôi đã đưa ra một giải pháp mới đơn giản hơn.
Thêm OP_SEGMENT
Hiện tại, trình xác thực sẽ quét toàn bộ tapscript để tìm opcode OP_SUCCESS
, và nếu tìm thấy, tập lệnh sẽ được chấp nhận. Mã này sẽ được thay đổi thành:
- Quét
OP_SEGMENT
vàOP_SUCCESSx
trong tapscript. - Nếu tìm thấy
OP_SEGMENT
, tập lệnh trước mã lệnh này sẽ được thực thi; nếu tập lệnh không bị lỗi, quá trình quét sẽ tiếp tục từ mã lệnh này. - Nếu tìm thấy
OP_SUCCESSx
, tập lệnh sẽ được truyền trực tiếp.
Về cơ bản, thao tác này chia một tập lệnh thành nhiều phân đoạn , mỗi phân đoạn được thực thi tuần tự. Nó không đơn giản như "chia tập lệnh thành các phân đoạn bằng OP_SEGMENT và chỉ thực thi một phân đoạn tại một thời điểm", vì tapscript cho phép bạn bao gồm nội dung không thể giải mã sau OP_SUCCESSx
, và chúng tôi muốn duy trì khả năng đó.
Nó không làm gì khi thực thi OP_SEGMENT
: nó chỉ tồn tại để giới hạn phạm vi bao phủ của mã lệnh OP_SUCCESS
.
hoàn thành
ExecuteWitnessScript
sẽ cần được cấu trúc lại (có thể là ExecuteTapScript
chuyên dụng, vì 21 trong số 38 dòng mã của nó nằm trong điều kiện "nếu Tapscript") và nó cũng gợi ý rằng giới hạn ngăn xếp cho tapscript hiện tại sẽ được áp dụng khi gặp OP_SEGMENT
, ngay cả khi theo sau là OP_SUCCESS
.
Điều thú vị là chức năng cốt lõi EvalScript
sẽ không thay đổi, ngoại trừ việc bỏ qua OP_SEGMENT
vì nó vốn đã rất linh hoạt.
Xin lưu ý, tôi vẫn chưa hoàn thiện việc triển khai nên có thể sẽ có điều bất ngờ, nhưng tôi dự định tạo ra nguyên mẫu sau khi ý tưởng này nhận được một số đánh giá.
Hy vọng bạn thích nó!
(qua)