Bethropic

Một lần làm tròn, không phải nhiều lần: tại sao trình xác minh khớp với máy chủ từng bit

uplandpebble0 lượt xem
🌐 Originally written in English· AI translationView original

Cái bẫy

Một hash là ba mươi hai byte. Một trò chơi cần một số từ 0 đến 1. Cách rõ ràng để có được nó là đọc một số byte dưới dạng số nguyên và chia cho giá trị lớn nhất có thể, và mọi người giải thích provably fair, bao gồm của chúng tôi, đều mô tả nó theo cách đó. Cách rõ ràng có một vấn đề chỉ xuất hiện khi hai chương trình khác nhau thực hiện nó.

Cả máy chủ và trình duyệt đều lưu trữ các số thông thường dưới dạng IEEE 754 doubles: 64 bit, trong đó 53 bit mang độ chính xác. Bảy byte của hash là 56 bit, tám byte là 64 bit, và cả hai không vừa. Vì vậy, việc chuyển đổi phải mất bit, và câu hỏi không phải là liệu nó có làm tròn hay không mà là bao nhiêu lần. Một vòng lặp tích lũy byte từng cái một, nhân tổng đang chạy với 256 và thêm byte tiếp theo, làm tròn ở mỗi bước một khi tổng vượt quá 2⁵³. Chuyển đổi số nguyên 64-bit thành double của Kotlin làm tròn chính xác một lần. Hai lần làm tròn cùng một giá trị không phải lúc nào cũng hạ cánh trên cùng một double như một lần.

Hầu hết thời gian không ai để ý, bởi vì sự khác biệt nằm trong bit cuối cùng của một số có mười sáu chữ số có nghĩa. Sau đó, một trò chơi nhân số với 37, hoặc 52, hoặc 1.000.000, và làm tròn xuống một số nguyên, và bit cuối cùng chính xác là bit quyết định liệu 36,9999999999999 trở thành 36 hay 37. Một trình xác minh được xây dựng theo cách rõ ràng sẽ từ chối một phần nhỏ các vòng hoàn toàn trung thực, và một người chơi thấy sự từ chối sẽ không có cách nào để phân biệt một lỗi làm tròn trung thực với một máy chủ không trung thực.

Cách engine bước qua nó

Sửa chữa là làm cho trình duyệt làm tròn chính xác bao nhiêu lần máy chủ làm, tức là một lần. Engine đọc bốn byte đầu tiên dưới dạng số nguyên, vừa vặn trong một double chính xác, và ba hoặc bốn byte tiếp theo dưới dạng một số nguyên khác, cũng vừa vặn chính xác. Mỗi cái được chia cho một lũy thừa của hai, một phép toán chính xác cho doubles. Sau đó, hai cái được thêm vào. Phép cộng đơn này là nơi duy nhất mất độ chính xác, và nó mất nó theo cách chuyển đổi đơn của Kotlin.

ENGINE-VERIFIED_shared/rng.ts u56: “first 7 bytes → uniform [0,1), matching the server’s Long→Double rounding bit-for-bit: hi/2^32 and lo/2^56 are both exact dyadic doubles, so the one IEEE addition rounds the 56-bit value exactly once — the same single rounding Kotlin’s toDouble() performs. (A naive 56-bit accumulate in a double would round repeatedly past 2^53 and can differ at floor edges.)” u64 uses the same construction over eight bytes, “matching the server’s ULong→Double conversion in LimboService.outcome100”, and is the function every seed-pair original imports.

Cách engine biến các byte thành một số, và mỗi lựa chọn bảo vệ cái gì

HÀMBYTES ĐỌCCẤU TRÚCKHỚP
u56bảy byte đầu tiênhi ÷ 2³² + lo ÷ 2⁵⁶, một phép cộngchuyển đổi Long → Double của máy chủ
u64tám byte đầu tiênhi ÷ 2³² + lo ÷ 2⁶⁴, một phép cộngchuyển đổi ULong → Double của máy chủ (Limbo)
h52 / bust1006.5 ký tự hex đầu tiên (13 chữ số hex)số học BigInt chính xác, không có double nào cảCrashDerivation.crashPoint100 của server

Mỗi seed-pair gốc (bingo, blackjack, hold'em, koban, omikuji, sic bo, video poker, hanabi, fukubukuro, roulette) nhập u64 từ rng.ts; bộ tạo và bộ xác minh là cùng một nhập.

Crash đưa đối số đến kết luận của nó. Điểm crash của nó tuân theo công thức bustabit, floor((100 × 2⁵² − h) ÷ (2⁵² − h)) trong đó h là 52 bit dẫn đầu của hash, và phép chia đó nằm đúng ở loại ranh giới nơi một double có thể sai lệch một. Vì vậy engine không sử dụng double. Nó tính công thức với số nguyên độ chính xác tùy ý, trong trình duyệt, cách mà server làm, và bình luận trong mã nguồn giải thích tại sao trong một dòng: một xấp xỉ float sẽ không đồng ý với phép chia số nguyên tại ranh giới floor và khiến bộ xác minh từ chối các vòng tốt.

ENGINE-VERIFIED_shared/rng.ts bust100: h = h52(digest), the first 13 hex characters as a BigInt; p = (100·2⁵² − h) ÷ (2⁵² − h) in BigInt division, clamped to [100, 1,000,000]; the comment reads “Exact BigInt math to match the server (NewWhiteBack CrashDerivation.crashPoint100) bit-for-bit — a float approximation would disagree with integer division at floor boundaries and make the verifier reject good rounds.” The BigInt values are built with BigInt() calls rather than literals so the file compiles under the project’s es5 target.

Một triển khai, hai công việc

Có một quyết định thứ hai, yên tĩnh hơn trong cùng một tệp. Các hàm biến hash thành số không được viết hai lần, một lần cho trò chơi và một lần cho trình kiểm tra. Chúng được viết một lần, trong một mô-đun mà tiêu đề mô tả là được chia sẻ bởi bộ tạo và bộ xác minh, và demo engine phát các kết quả cho các vòng không đăng nhập trong trình duyệt của bạn bằng cách gọi các hàm tương tự mà bảng công bằng gọi để kiểm tra chúng.

ENGINE-VERIFIED_shared/rng.ts header: “Crypto primitives shared by the provably-fair GENERATOR (demoLocal.ts, the per-game derive modules) and the VERIFIER (fairness.tsx). Extracted from fairness.tsx so a game’s derivation can be imported by both sides without a circular import.” hmacSha256Utf8 is described as consuming key and message “exactly as RandomUtils.generateHash consumes them on the server … one implementation, so the demo can never drift from what the verifier accepts.”

Thiết kế đó có một hệ quả dễ dàng phát biểu và đáng phát biểu. Khi bộ xác minh nói một vòng kiểm tra ra ngoài, nó không nói rằng xấp xỉ của riêng nó về server đồng ý với server trong một dung sai nào đó. Nó nói rằng cùng một hàm, được cung cấp các đầu vào giống nhau, đã tạo ra cùng một đầu ra, và không có dung sai vì không có gì để dung sai. Khi nó nói một vòng không kiểm tra ra, đó không phải là tiếng ồn. Nó có nghĩa là các đầu vào khác nhau, đó là điều duy nhất mà bộ xác minh tồn tại để phát hiện.

Một bộ xác minh làm tròn khác với server là một bộ xác minh đôi khi kêu gọi sói. Sau cảnh báo sai lầm đầu tiên, không ai nghe thấy cảnh báo thực sự.tại sao việc làm tròn lại quan trọng

Cái cần lấy từ nó

  • "Công bằng có thể chứng minh" là một tuyên bố về số học, và số học có các cạnh. Sơ đồ cam kết là tiêu đề; dẫn xuất từng bit là những gì làm cho tiêu đề có thể thực thi được.
  • Sự không khớp hợp nên hiếm đến nỗi nó đáng báo động. Trên engine này, một vòng trung thực không thể thất bại xác minh vì làm tròn, vì vậy một thất bại là thông tin chứ không phải là một hiện tượng kỹ thuật.
  • Bạn có thể đọc hàm. Cấu trúc là một vài dòng, và hướng dẫn xác minh cho biết nơi mỗi dòng được sử dụng trên một vòng trực tiếp.

Bài viết này mô tả mô-đun dẫn xuất phía khách hàng và trích dẫn các bình luận của nó về server mà nó phản chiếu; chính server không nằm trong kho lưu trữ công khai, và dịch vụ nhà là cơ quan thanh toán. Nó là một ghi chú kỹ thuật, không phải là chứng chỉ.

FAQ

Tại sao bộ xác minh và server sẽ không đồng ý nếu chúng sử dụng cùng một hash?

Vì việc biến hash thành số từ 0 đến 1 yêu cầu làm tròn thành 53 bit độ chính xác, và làm tròn một lần không phải lúc nào cũng cho cùng một kết quả như làm tròn nhiều lần. Một bộ xác minh tích lũy byte trong một vòng lặp có thể khác với server chuyển đổi một lần, ở bit cuối cùng.

Một bit có thực sự quan trọng không?

Khi số được nhân với 37 hoặc 52 hoặc một triệu và làm tròn xuống, bit cuối cùng có thể quyết định số nguyên nào sẽ được kết quả. Đó là một pocket, card hoặc item khác.

Engine tránh điều đó như thế nào?

Nó xây dựng số từ hai phần chính xác có thể biểu diễn được, bốn byte đầu tiên trên 2³² và các byte tiếp theo trên 2⁵⁶ hoặc 2⁶⁴, và cộng chúng một lần, khớp với làm tròn đơn của chuyển đổi integer-to-double của máy chủ. Crash sử dụng số học integer chính xác và không có doubles nào cả.

Verifier có phải là một chương trình riêng biệt với trò chơi không?

Không. Các hàm hash và number nằm trong một module dùng chung mà cả demo engine và bảng fairness đều import, vì vậy generator không thể lệch khỏi verifier.

Failed verification có nghĩa gì ở đây?

Rằng các input khác với những gì máy chủ sử dụng, vì làm tròn không thể gây ra một thất bại sai trên engine này. Đó là tín hiệu mà verifier tồn tại để đưa ra.

SOURCES & REFERENCES

  • Betkyo engine source: _shared/rng.ts (u56, u64, h52, bust100, hmacSha256Utf8 và các comment của chúng)
  • IEEE 754-2019 — Standard for Floating-Point Arithmetic (binary64: 53 bits of significand precision)
  • Bustabit provably fair crash-point formula, the construction bust100 mirrors

THE GAMES IN THIS ARTICLE

Limbo — rules & free play →Crash — rules & free play →Roulette — rules & free play →

Một lần làm tròn, không phải nhiều lần: tại sao trình xác minh khớp với máy chủ từng bit

Bình luận (1)

  • greypebble473

    The part about the last bit deciding between 36 and 37 is wild, that's such a tiny margin for error to cause rejections.