Logo Zephyrnet

[Gương] Chương trình số học bậc hai: từ số 0 đến anh hùng

Ngày:

Vitalik Buterin thông qua Blog Vitalik Buterin

Đây là sự phản ánh của bài viết tại https://medium.com/@VitalikButerin/chương trình số học bậc hai-từ-zero-to-hero-f6d558cea649

Gần đây có rất nhiều sự quan tâm đến công nghệ đằng sau zk-SNARK và mọi người ngày càng quan tâm nhiều hơn đến đang cố gắng làm sáng tỏ thứ mà nhiều người gọi là “toán học mặt trăng” do tính phức tạp không thể giải mã được của nó. zk-SNARK thực sự khá khó nắm bắt, đặc biệt là do số lượng lớn các bộ phận chuyển động cần kết hợp với nhau để toàn bộ hoạt động, nhưng nếu chúng ta chia công nghệ ra từng phần một thì việc hiểu nó sẽ trở nên đơn giản hơn.

Mục đích của bài đăng này không nhằm mục đích giới thiệu đầy đủ về zk-SNARK; nó giả định kiến ​​thức nền tảng rằng (i) bạn biết zk-SNARK là gì và chúng làm gì, đồng thời (ii) biết đủ toán để có thể suy luận về những thứ như đa thức (nếu câu lệnh �(�)+�(�) =(�+�)(�) , trong đó � và � là đa thức, có vẻ tự nhiên và hiển nhiên đối với bạn, thì bạn đã ở đúng cấp độ). Thay vào đó, bài đăng đào sâu hơn vào bộ máy đằng sau công nghệ và cố gắng giải thích rõ ràng nhất có thể về nửa đầu của quy trình, như nhà nghiên cứu zk-SNARK Eran Tromer đã vẽ ở đây:

Các bước ở đây có thể được chia thành hai nửa. Đầu tiên, zk-SNARK không thể được áp dụng trực tiếp cho bất kỳ vấn đề tính toán nào; đúng hơn, bạn phải chuyển vấn đề sang “dạng” phù hợp để giải quyết vấn đề. Biểu mẫu này được gọi là “chương trình số học bậc hai” (QAP) và việc chuyển đổi mã của một hàm thành một trong các mã này bản thân nó là điều hết sức không cần thiết. Cùng với quy trình chuyển đổi mã của hàm thành QAP là một quy trình khác có thể được chạy song song để nếu bạn có đầu vào cho mã, bạn có thể tạo giải pháp tương ứng (đôi khi được gọi là “làm chứng” cho QAP). Sau đó, có một quy trình khá phức tạp khác để tạo ra “bằng chứng không có kiến ​​thức” thực tế cho nhân chứng này và một quy trình riêng để xác minh bằng chứng mà người khác đã chuyển cho bạn, nhưng đây là những chi tiết nằm ngoài phạm vi của bài đăng này. .

Ví dụ mà chúng ta sẽ chọn là một ví dụ đơn giản: chứng minh rằng bạn biết nghiệm của phương trình bậc ba: �3+�+5=35 (gợi ý: đáp án là 3). Vấn đề này đủ đơn giản để QAP thu được sẽ không lớn đến mức đáng sợ, nhưng đủ nhỏ để bạn có thể thấy tất cả các máy móc đều hoạt động.

Hãy để chúng tôi viết ra chức năng của chúng tôi như sau:

def qeval(x):
    y = x**3
    return x + y + 5

Ngôn ngữ lập trình có mục đích đặc biệt đơn giản mà chúng tôi đang sử dụng ở đây hỗ trợ số học cơ bản (+, −, ⋅, /), lũy thừa lũy thừa không đổi (�7 chứ không phải ��) và phép gán biến, đủ mạnh để về mặt lý thuyết bạn có thể làm được bất kỳ tính toán nào bên trong nó (miễn là số bước tính toán bị giới hạn; không cho phép vòng lặp). Lưu ý rằng các toán tử modulo (%) và toán tử so sánh (<, >, ≤, ≥) KHÔNG được hỗ trợ, vì không có cách nào hiệu quả để thực hiện modulo hoặc so sánh trực tiếp trong số học nhóm tuần hoàn hữu hạn (hãy biết ơn vì điều này; nếu có một cách để thực hiện một trong hai, thì mật mã đường cong elip sẽ bị phá vỡ nhanh hơn bạn có thể nói “tìm kiếm nhị phân” và “định lý số dư Trung Hoa”).

Bạn có thể mở rộng ngôn ngữ sang modulo và so sánh bằng cách cung cấp các phân tách bit (ví dụ: 13=23+22+1) làm đầu vào phụ, chứng minh tính đúng đắn của các phân tách đó và thực hiện phép toán trong mạch nhị phân; trong số học trường hữu hạn, việc thực hiện kiểm tra đẳng thức (==) cũng có thể thực hiện được và trên thực tế còn dễ dàng hơn một chút, nhưng đây đều là những chi tiết mà chúng ta sẽ không đề cập ngay bây giờ. Chúng ta có thể mở rộng ngôn ngữ để hỗ trợ các điều kiện (ví dụ: if �<5:�=7; else: �=9) bằng cách chuyển đổi chúng sang dạng số học: �=7⋅(�<5)+9⋅(� ≥5 ) tuy nhiên hãy lưu ý rằng cả hai “đường dẫn” của điều kiện sẽ cần phải được thực thi và nếu bạn có nhiều điều kiện lồng nhau thì điều này có thể dẫn đến một lượng lớn chi phí.

Bây giờ chúng ta hãy đi qua quá trình này từng bước một. Nếu bạn muốn tự mình làm điều này với bất kỳ đoạn mã nào, tôi đã triển khai trình biên dịch tại đây (chỉ dành cho mục đích giáo dục; chưa sẵn sàng để tạo QAP cho zk-SNARK trong thế giới thực!).

Làm phẳng

Bước đầu tiên là quy trình “làm phẳng”, trong đó chúng tôi chuyển đổi mã gốc, có thể chứa các câu lệnh và biểu thức phức tạp tùy ý, thành một chuỗi các câu lệnh có hai dạng: �=� (trong đó � có thể là một biến hoặc một số ) và �=� (��) � (trong đó �� có thể là +, −, ⋅, / và � và � có thể là biến, số hoặc chính chúng là biểu thức con). Bạn có thể coi mỗi câu lệnh này giống như các cổng logic trong một mạch điện. Kết quả của quá trình làm phẳng cho đoạn mã trên như sau:

sym_1 = x * x
y = sym_1 * x
sym_2 = y + x
~out = sym_2 + 5

Nếu bạn đọc mã gốc và mã ở đây, bạn có thể dễ dàng thấy rằng cả hai đều tương đương nhau.

Cổng vào R1CS

Bây giờ, chúng tôi chuyển đổi hệ thống này thành một thứ gọi là hệ thống ràng buộc cấp 1 (R1CS). R1CS là một chuỗi gồm các nhóm gồm ba vectơ (�, �, �) và nghiệm của R1CS là vectơ �, trong đó � phải thỏa mãn phương trình �.�⋅�.�−�.�=0, trong đó . đại diện cho tích số chấm – nói một cách đơn giản hơn, nếu chúng ta “kết hợp” � và �, nhân hai giá trị ở cùng một vị trí, rồi lấy tổng của các tích này, sau đó thực hiện tương tự với � và � rồi � và � , thì kết quả thứ ba bằng tích của hai kết quả đầu tiên. Ví dụ: đây là một R1CS hài lòng:

Nhưng thay vì chỉ có một ràng buộc, chúng ta sẽ có nhiều ràng buộc: một ràng buộc cho mỗi cổng logic. Có một cách tiêu chuẩn để chuyển đổi một cổng logic thành bộ ba (�,�,�) tùy thuộc vào phép toán là (+, −, ⋅ hoặc /) và các đối số là biến hay số. Độ dài của mỗi vectơ bằng tổng số biến trong hệ thống, bao gồm một biến giả ~một ở chỉ số đầu tiên đại diện cho số 1, các biến đầu vào, một biến giả ~out đại diện cho đầu ra và sau đó là tất cả các biến các biến trung gian (���1 và ���2 ở trên); các vectơ nhìn chung sẽ rất thưa thớt, chỉ điền vào các vị trí tương ứng với các biến bị ảnh hưởng bởi một số cổng logic cụ thể.

Đầu tiên, chúng tôi sẽ cung cấp ánh xạ biến mà chúng tôi sẽ sử dụng:

'~one', 'x', '~out', 'sym_1', 'y', 'sym_2'

Vectơ giải pháp sẽ bao gồm các phép gán cho tất cả các biến này theo thứ tự đó.

Bây giờ, chúng ta sẽ đưa ra bộ ba (�,�,�) cho cổng đầu tiên:

a = [0, 1, 0, 0, 0, 0]
b = [0, 1, 0, 0, 0, 0]
c = [0, 0, 0, 1, 0, 0]

Bạn có thể thấy rằng nếu vectơ nghiệm chứa 3 ở vị trí thứ hai và 9 ở vị trí thứ tư thì bất kể nội dung còn lại của vectơ nghiệm là gì, việc kiểm tra tích số chấm sẽ rút gọn thành 3⋅3=9, và thế là nó sẽ qua. Nếu vectơ nghiệm có −3 ở vị trí thứ hai và 9 ở vị trí thứ tư thì phép kiểm tra cũng sẽ đạt; trên thực tế, nếu vectơ giải pháp có 7 ở vị trí thứ hai và 49 ở vị trí thứ tư thì kiểm tra đó sẽ vẫn đạt - mục đích của kiểm tra đầu tiên này là để xác minh tính nhất quán của đầu vào và đầu ra của cổng đầu tiên.

Bây giờ chúng ta sang cổng thứ hai:

a = [0, 0, 0, 1, 0, 0]
b = [0, 1, 0, 0, 0, 0]
c = [0, 0, 0, 0, 1, 0]

Theo cách tương tự như kiểm tra tích số chấm đầu tiên, ở đây chúng ta đang kiểm tra ���1⋅�=�.

Bây giờ là cổng thứ ba:

a = [0, 1, 0, 0, 1, 0]
b = [1, 0, 0, 0, 0, 0]
c = [0, 0, 0, 0, 0, 1]

Ở đây, mẫu hơi khác một chút: đó là nhân phần tử đầu tiên trong vectơ nghiệm với phần tử thứ hai, sau đó với phần tử thứ năm, cộng hai kết quả và kiểm tra xem tổng có bằng phần tử thứ sáu hay không. Vì phần tử đầu tiên trong vectơ nghiệm luôn là một nên đây chỉ là phép kiểm tra phép cộng, kiểm tra xem đầu ra có bằng tổng của hai đầu vào hay không.

Cuối cùng là cổng thứ tư:

a = [5, 0, 0, 0, 0, 1]
b = [1, 0, 0, 0, 0, 0]
c = [0, 0, 1, 0, 0, 0]

Ở đây, chúng ta đang đánh giá lần kiểm tra cuối cùng, ~out =���2+5. Kiểm tra tích số chấm hoạt động bằng cách lấy phần tử thứ sáu trong vectơ nghiệm, cộng năm lần phần tử đầu tiên (nhắc nhở: phần tử đầu tiên là 1, vì vậy điều này thực chất có nghĩa là thêm 5) và kiểm tra nó với phần tử thứ ba, đó là nơi chúng ta lưu trữ biến đầu ra.

Và ở đó chúng ta có R1CS với bốn ràng buộc. Nhân chứng chỉ đơn giản là sự gán cho tất cả các biến, bao gồm các biến đầu vào, đầu ra và nội bộ:

[1, 3, 35, 9, 27, 30]

Bạn có thể tự mình tính toán điều này bằng cách đơn giản là “thực thi” đoạn mã đã được làm phẳng ở trên, bắt đầu bằng phép gán biến đầu vào �=3 và nhập giá trị của tất cả các biến trung gian và đầu ra khi bạn tính toán chúng.

R1CS hoàn chỉnh được ghép lại với nhau là:

A
[0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0]
[0, 1, 0, 0, 1, 0]
[5, 0, 0, 0, 0, 1]

B
[0, 1, 0, 0, 0, 0]
[0, 1, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0]

C
[0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 1]
[0, 0, 1, 0, 0, 0]

R1CS sang QAP

Bước tiếp theo là lấy R1CS này và chuyển đổi nó thành dạng QAP, thực hiện logic tương tự ngoại trừ việc sử dụng đa thức thay vì tích số chấm. Chúng tôi làm điều này như sau. Chúng ta đi từ bốn nhóm ba vectơ có độ dài từ sáu đến sáu nhóm đa thức ba bậc 3, trong đó việc đánh giá các đa thức ở mỗi nhóm tọa độ x đại diện cho một trong những hạn chế. Nghĩa là, nếu chúng ta định giá các đa thức ở �=1, thì chúng ta sẽ có được tập vectơ đầu tiên, nếu chúng ta định giá các đa thức ở �=2, thì chúng ta sẽ có được tập vectơ thứ hai, v.v.

Chúng ta có thể thực hiện sự chuyển đổi này bằng cách sử dụng một thứ gọi là Nội suy Lagrange. Vấn đề mà phép nội suy Lagrange giải quyết là: nếu bạn có một tập hợp các điểm (tức là các cặp tọa độ (�,�)), thì việc thực hiện phép nội suy Lagrange trên các điểm đó sẽ cho bạn một đa thức đi qua tất cả các điểm đó. Chúng ta thực hiện điều này bằng cách giải bài toán: với mỗi tọa độ �, chúng ta tạo một đa thức có tọa độ � mong muốn tại tọa độ � đó và tọa độ � bằng 0 ở tất cả các tọa độ � khác mà chúng ta quan tâm, và sau đó thu được kết quả cuối cùng kết quả là chúng ta cộng tất cả các đa thức lại với nhau.

Hãy làm một ví dụ. Giả sử chúng ta muốn một đa thức đi qua (1,3),(2,2) và (3,4). Chúng ta bắt đầu bằng cách tạo một đa thức đi qua (1,3),(2,0) và (3,0). Hóa ra, việc tạo ra một đa thức “nhô ra” tại �=1 và bằng XNUMX tại các điểm quan tâm khác là điều dễ dàng; chúng tôi chỉ đơn giản làm:

(x - 2) * (x - 3)

Trông như thế này:

Bây giờ, chúng ta chỉ cần “rescale” nó sao cho chiều cao tại x=1 là đúng:

(x - 2) * (x - 3) * 3 / ((1 - 2) * (1 - 3))

Điều này cho chúng ta:

1.5 * x**2 - 7.5 * x + 9

Sau đó, chúng ta làm tương tự với hai điểm còn lại và nhận được hai đa thức trông giống nhau khác, ngoại trừ việc chúng “nổi bật” tại �=2 và �=3 thay vì �=1. Chúng tôi cộng cả ba lại với nhau và nhận được:

1.5 * x**2 - 5.5 * x + 7

Với chính xác tọa độ mà chúng ta mong muốn. Thuật toán như được mô tả ở trên cần �(�3) thời gian, vì có � điểm và mỗi điểm cần �(�2) thời gian để nhân các đa thức với nhau; chỉ cần suy nghĩ một chút, điều này có thể giảm xuống còn �(�2) thời gian và nếu suy nghĩ nhiều hơn, bằng cách sử dụng thuật toán biến đổi Fourier nhanh và tương tự, nó có thể được giảm hơn nữa - một sự tối ưu hóa quan trọng khi các hàm được sử dụng trong zk -SNARK trong thực tế thường có hàng ngàn cổng.

Bây giờ, hãy sử dụng phép nội suy Lagrange để biến đổi R1CS của chúng ta. Những gì chúng ta sắp làm là lấy giá trị đầu tiên ra khỏi mỗi vectơ �, sử dụng phép nội suy Lagrange để tạo ra đa thức từ giá trị đó (trong đó việc đánh giá đa thức tại � sẽ mang lại cho bạn giá trị đầu tiên của vectơ �th �), lặp lại quy trình đối với giá trị đầu tiên của mọi vectơ � và �, sau đó lặp lại quá trình đó cho các giá trị thứ hai, giá trị thứ ba, v.v. Để thuận tiện, tôi sẽ cung cấp câu trả lời ngay bây giờ:

A polynomials
[-5.0, 9.166, -5.0, 0.833]
[8.0, -11.333, 5.0, -0.666]
[0.0, 0.0, 0.0, 0.0]
[-6.0, 9.5, -4.0, 0.5]
[4.0, -7.0, 3.5, -0.5]
[-1.0, 1.833, -1.0, 0.166]

B polynomials
[3.0, -5.166, 2.5, -0.333]
[-2.0, 5.166, -2.5, 0.333]
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]

C polynomials
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]
[-1.0, 1.833, -1.0, 0.166]
[4.0, -4.333, 1.5, -0.166]
[-6.0, 9.5, -4.0, 0.5]
[4.0, -7.0, 3.5, -0.5]

Các hệ số được sắp xếp tăng dần nên đa thức đầu tiên ở trên thực tế là 0.833⋅�3—5⋅�2+9.166⋅�−5. Tập hợp đa thức này (cộng với đa thức Z mà tôi sẽ giải thích sau) tạo nên các tham số cho phiên bản QAP cụ thể này. Lưu ý rằng tất cả công việc cho đến thời điểm này chỉ cần được thực hiện một lần cho mọi chức năng mà bạn đang cố gắng sử dụng zk-SNARK để xác minh; khi các tham số QAP được tạo, chúng có thể được sử dụng lại.

Hãy thử đánh giá tất cả các đa thức này tại �=1. Việc tính một đa thức tại �=1 đơn giản có nghĩa là cộng tất cả các hệ số (là 1�=1 với mọi �), nên không khó. Chúng tôi nhận được:

A results at x=1
0
1
0
0
0
0

B results at x=1
0
1
0
0
0
0

C results at x=1
0
0
0
1
0
0

Và kìa, những gì chúng ta có ở đây giống hệt như bộ ba vectơ cho cổng logic đầu tiên mà chúng ta đã tạo ở trên.

Kiểm tra QAP

Bây giờ mục đích của sự biến đổi điên rồ này là gì? Câu trả lời là thay vì kiểm tra các ràng buộc trong R1CS riêng lẻ, giờ đây chúng ta có thể kiểm tra tất cả các ràng buộc cùng một lúc bằng cách thực hiện kiểm tra sản phẩm chấm về đa thức.

Bởi vì trong trường hợp này, việc kiểm tra tích số chấm là một chuỗi các phép cộng và phép nhân của các đa thức, kết quả sẽ là một đa thức. Nếu đa thức kết quả, được đánh giá ở mọi tọa độ � mà chúng ta đã sử dụng ở trên để biểu thị một cổng logic, bằng 1 thì điều đó có nghĩa là tất cả các bước kiểm tra đều đạt; nếu đa thức kết quả được đánh giá ít nhất một trong các tọa độ � đại diện cho một cổng logic cho giá trị khác 2, thì điều đó có nghĩa là các giá trị đi vào và ra khỏi cổng logic đó không nhất quán (tức là cổng là �=�⋅�� �1 nhưng các giá trị được cung cấp có thể là �=2,���5=XNUMX và �=XNUMX).

Lưu ý rằng bản thân đa thức thu được không nhất thiết phải bằng 0 và trên thực tế, trong hầu hết các trường hợp sẽ không bằng 0; nó có thể có bất kỳ hành vi nào tại các điểm không tương ứng với bất kỳ cổng logic nào, miễn là kết quả bằng 0 tại tất cả các điểm do tương ứng với một cổng nào đó. Để kiểm tra tính đúng đắn, chúng ta thực sự không đánh giá đa thức �=�.�⋅�.�−�.� tại mọi điểm tương ứng với một cổng; thay vào đó, chúng ta chia � cho một đa thức khác, �, và kiểm tra xem � có chia đều � – nghĩa là phép chia �/� không để lại số dư.

� được định nghĩa là (�−1)⋅(�−2)⋅(�−3)… – đa thức đơn giản nhất bằng XNUMX tại tất cả các điểm tương ứng với cổng logic. Đó là một thực tế cơ bản của đại số bất kì đa thức bằng 0 tại tất cả các điểm này phải là bội số của đa thức tối thiểu này, và nếu một đa thức là bội số của � thì đánh giá của nó tại bất kỳ điểm nào trong số đó sẽ bằng 0; sự tương đương này làm cho công việc của chúng tôi dễ dàng hơn nhiều.

Bây giờ, chúng ta hãy thực hiện việc kiểm tra tích chấm với các đa thức ở trên. Đầu tiên, các đa thức trung gian:

A . s = [43.0, -73.333, 38.5, -5.166]
B . s = [-3.0, 10.333, -5.0, 0.666]
C . s = [-41.0, 71.666, -24.5, 2.833]

Bây giờ, �.�⋅�.�—�.�:

t = [-88.0, 592.666, -1063.777, 805.833, -294.777, 51.5, -3.444]

Bây giờ, đa thức tối thiểu �=(�−1)⋅(�−2)⋅(�−3)⋅(�−4):

Z = [24, -50, 35, -10, 1]

Và nếu chúng ta chia kết quả trên cho �, chúng ta nhận được:

h = t / Z = [-3.666, 17.055, -3.444]

Không có phần còn lại.

Và thế là chúng ta có giải pháp cho QAP. Nếu chúng ta cố gắng làm sai lệch bất kỳ biến nào trong giải pháp R1CS mà chúng ta đang rút ra giải pháp QAP này - chẳng hạn, đặt biến cuối cùng thành 31 thay vì 30, thì chúng ta sẽ nhận được một đa thức � không thực hiện được một trong các bước kiểm tra (đặc biệt là trường hợp, kết quả tại �=3 sẽ bằng −1 thay vì 0), và hơn nữa � sẽ không phải là bội số của �; đúng hơn, chia �/� sẽ cho số dư là [−5.0,8.833,−4.5,0.666].

Lưu ý rằng ở trên là sự đơn giản hóa; “trong thế giới thực”, phép cộng, nhân, trừ và chia sẽ không xảy ra với các số thông thường mà với các phần tử trường hữu hạn - một loại số học ma quái tự nhất quán, vì vậy tất cả các định luật đại số mà chúng ta biết và yêu thích vẫn còn đúng, nhưng trong đó tất cả các câu trả lời đều là phần tử của một số tập hợp có kích thước hữu hạn, thường là các số nguyên trong phạm vi từ 0 đến �−1 đối với một số �. Ví dụ: nếu �=13, thì 1/2=7 (và 7⋅2=1),3⋅5=2, v.v. Việc sử dụng số học trường hữu hạn sẽ loại bỏ nhu cầu lo lắng về lỗi làm tròn và cho phép hệ thống hoạt động tốt với các đường cong elip, điều này trở nên cần thiết đối với phần còn lại của bộ máy zk-SNARK giúp giao thức zk-SNARK thực sự an toàn.

Đặc biệt cảm ơn Eran Tromer vì đã giúp giải thích nhiều chi tiết về hoạt động bên trong của zk-SNARK cho tôi.

tại chỗ_img

Tin tức mới nhất

tại chỗ_img