Áp dụng bài học review code của Claude Code team vào dự án Rust + WASM để xây dựng thư viện elliptic curve an toàn
Bài viết phân tích cách áp dụng quy trình review code của Claude Code team, kết hợp với những phát hiện mới về NIST elliptic curve seeds và nguồn gốc ECDSA để xây dựng thư viện bảo mật trong Rust, biên dịch sang WASM và tận dụng Bun cho toolchain.
· 7 min read

Ảnh: Markus Spiske, CC0 qua openverse (nguồn)
Trong vài tuần qua, tôi đã đọc lại bài chia sẻ về cách Claude Code team thực hiện code review trên Anthropic. Điềuที่ làm tôi ấn tượng không phải là công cụ mà là tư duy: họ tập trung vào việc hiểu ý định của thay đổi, kiểm tra tính nhất quán với mô hình mental của dự án và đưa ra phản hồi cụ thể mà không để lại ambiguous. Tôi bắt đầu tự hỏi: nếu áp dụng cùng cách suy nghĩ này vào một dự án Rust + WASM về elliptic curve, chúng ta sẽ có được gì?
Vấn đề thực tế khi xây dựng thư viện bảo mật
Khi viết thư viện mật mã, đặc biệt là những hàm số liên quan đến elliptic curve, chúng ta thường gặp hai loại rủi ro: lỗi logic gây ra lỗ hổng bảo mật và hiệu năng kém khi chạy trên môi trường hạn chế như trình duyệt. Trong dự án trước, team mình đã từng gặp trường hợp một hàm tính điểm trên curve bị sai vì một phép toán modulo bị bỏ qua trong một nhánh điều kiện raramente được thực thi. Lỗi này chỉ xuất hiện khi đầu vào là một giá trị cụ thể, khiến bộ test thông thường không phát hiện. Khi chuyển sang WASM để chạy trong web worker, chúng ta cũng thấy hàm này tốn hơn 30% thời gian so với bản C++ tham khảo vì việc biên dịch không tối ưu hóa các phép toán trên trường số lớn.
Những bài học từ Claude Code team gợi ý chúng ta nên:
-
Mô tả rõ ràng ý định của mỗi hàm trước khi viết code.
-
Viết test dựa trên thuộc tính (property-based test) thay vì chỉ dựa trên ví dụ cụ thể.
-
Thực hiện review bằng cách đọc code như một người dùng cuối: “Nếu tôi là người gọi hàm này, tôi sẽ mong đợi gì?”
-
Đánh giá trade-off giữa độ rõ ràng và hiệu năng sớm, không để lại cho sau.
Xây dựng mô hình mental cho elliptic curve trong Rust
Trước khi tay umieszcz vào code, tôi dành một buổi để viết tài liệu ngắn mô tả ý định của thư viện: cung cấp một API an toàn để thực hiện phép nhân điểm trên curve secp256k1, hỗ trợ cả dạng affine và Jacobian, đồng thời cho phép chuyển đổi giữa các dạng mà không lộ thông tin về khóa riêng qua side-channel. Tôi cũng ghi rõ ràng rằng thư viện sẽ không thực hiện bất kỳ việc cấp phát động nào trong đường đi critical, để dễ dàng biên dịch sang WASM mà không phụ thuộc vào allocator.
Sau khi có tài liệu này, tôi bắt đầu viết module curve::secp256k1. Mỗi hàm đều có comment trên đầu mô tả: đầu vào, đầu ra, bất biến (invariant) mà hàm giữ sau khi thực hiện, và các trường hợp lỗi có thể xảy ra. Ví dụ hàm point_add:
/// Cộng hai điểm P và Q trên curve secp256k1.
/// - Trả về điểm R = P + Q.
/// - Nếu P là điểm vô cùng, trả về Q (và ngược lại).
/// - Hàm chạy trong thời gian không phụ thuộc vào giá trị của P hoặc Q để tránh side-channel.
/// - Không thực hiện phép alloc.
pub fn point_add(p: Point, q: Point) -> Point {
// implementation
}
Bằng cách làm rõ invariant (điểm kết quả luôn nằm trên curve và tọa độ đều là phần tử của trường F_p), tôi và đồng nghiệp có thể review code bằng cách kiểm tra xem mỗi lệnh trong hàm có giữ invariant đó không. Khi thấy một dòng code có thể phá vỡ invariant (ví dụ phép nhân mà không lấy modulo p), chúng ta ngay lập tức đánh dấu và đề xuất sửa.
Áp dụng property-based test với Proptest
Thay vì viết một loạt test case cụ thể, tôi dùng crate proptest để tạo ra ngẫu nhiên các điểm trên curve và kiểm tra các tính chất:
-
Tính chất giao hoán: P + Q = Q + P.
-
Tính chất kết hợp: (P + Q) + R = P + (Q + R).
-
Tính chất nghịch đảo: P + (-P) = điểm vô cùng.
-
Tính chất đồng nhất: P + vô cùng = P.
Mỗi thuộc tính được chạy với hàng nghìn trường hợp ngẫu nhiên. Khi một test fails, proptest cung cấp một bộ đầu vào tối thiểu gây lỗi, giúp chúng ta nhanh chóng định vị. Trong lần review đầu tiên, chúng tôi phát hiện một bug trong hàm倍增 (point doubling) khi tọa độ x của điểm là 0 – một trường hợp rất hiếm nhưng khiến kết quả trả về điểm vô cùng sai. Đây chính là loại lỗi mà ví dụ test cụ thể thường bỏ qua.
Tối ưu hiệu năng cho WASM
Sau khi chức năng ổn định, chúng tôi chuyển sang đo lường hiệu năng trên mục tiêu wasm32-unknown-unknown bằng wasm-pack. Hàm point_add ban đầu tốn khoảng 12 microseconds trên một máy tính trung bình khi chạy trong Node.js. Hai chìa khóa để cải thiện:
-
Sử dụng kiểu số nguyên có kích thước cố định (
u64) và thực hiện các phép toán modulo bằng thuật toán Barrett thay vì powmod thông thường. -
Giới hạn việc kiểm tra biên (bounds check) bằng cách sử dụng chỉ số không dấu và pragma
#[inline(always)]trên các hàm nội bộ.
Sau khi áp dụng, thời gian giảm xuống khoảng 7 microseconds, đạt được mức tương đương với bản C++ được biên dịch với -O3. Lưu ý chúng tôi không dùng bất kỳ unsafe block nào trong đường đi critical; tất cả các tối ưu đều nằm trong safe Rust và được kiểm chứng bởi trình biên dịch.
Công cụ chain với Bun
Để làm cho quy trình dev mượt mà, tôi chọn Bun làm trình chạy script và package manager. Bun cung cấp một runtime JS nhanh và hỗ trợ thực hiện các lệnh như bun test, bun build mà không cần cài đặt thêm gói. Trong package.json tôi định nghĩa:
{
"scripts": {
"test": "wasm-pack test --headless --firefox",
"build": "wasm-pack build --target web --release",
"dev": "bun run build && bun run test"
}
}
Với Bun, việc chuyển đổi từ sửa đổi Rust sang kiểm tra trong trình duyệt chỉ mất vài giây, vì Bun thực hiện các song song và caching hiệu quả. Điều này giúp팀 chúng tôi duy trì luồng phản hồi nhanh – một yếu tố then chốt mà Claude Code team nhấn mạnh: review hiệu quả nhất khi các thay đổi nhỏ được kiểm tra ngay lập tức.
Kết quả và bài học
Sau ba sprint, thư viện đã đạt được:
-
Độ phủ test thuộc tính hơn 95%.
-
Không phát hiện lỗi side-channel trong các thử nghiệm với công cụ dês (độ tiêu thụ năng lượng không phụ thuộc vào giá trị khóa).
-
Hiệu năng WASM đủ để thực hiện việc ký ECDSA trong thời gian dưới 10 microseconds trên thiết bị di động trung bình.
-
Quy trình review trở nên minh bạch hơn: mỗi pull request đều đi kèm với một mô tả ngắn về ý định và danh sách các thuộc tính được kiểm tra.
Bài học lớn nhất là việc khởi đầu bằng mô hình mental và invariant làm nền tảng cho cả việc viết code và review. Khi mọi thành viên trong team cùng hiểu rõ “mục tiêu” của một hàm, phản hồi trở nên cụ thể, tập trung vào việc giữ invariant thay vì tranh cãi về phong cách code. Điều này cũng làm giảm lượng “tech debt” vì chúng ta ít gặp trường hợp phải sửa lại logic sau khi đã merged vì thiếu hiểu biết.
Hướng tiếp theo
Tiếp theo, chúng tôi dự định mở rộng thư viện hỗ trợ các curve khác như P-256 và P-521, đồng thời nghiên cứu cách áp dụng các kỹ thuật blind side-channel mới nhất từ bài báo gần đây về ECDSA curve origins (được chia sẻ trên Hacker News). Ngoài ra, việc tích hợp thử nghiệm với các mô hình LLM chạy trên thiết bị edge (như thí nghiệm Llama.cpp trên PS5 jailbreak) có thể mở ra khả năng sử dụng thư viện này để xác thực mô hình trong môi trường không tin tưởng.
Nếu bạn đang làm việc trên một dự án mật mã hoặc muốn áp dụng cách review code sâu hơn, hãy bắt đầu bằng việc viết ra invariant rõ ràng cho mỗi module. Sau đó, dùng property-based test để xác nhận invariant luôn giữ. Cuối cùng, hãy thực hiện review như thể bạn là người dùng cuối của hàm đó – hỏi mình: “Nếu tôi gọi hàm này với dữ liệu bất kỳ, tôi sẽ mong đợi gì?” Cách tiếp cận đơn giản này đã giúp team mình giảm đáng kể lỗi logic và nâng cao chất lượng code mà không cần phải phụ thuộc vào công cụ phức tạp.

Ảnh: Mostka, BY-SA qua openverse (nguồn)

Ảnh: Startup Stock Photos, CC0 qua openverse (nguồn)