-
Notifications
You must be signed in to change notification settings - Fork 11.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Examples/Move] Port remaining Crypto Sui Programmability Examples
## Description Port over the following examples from sui_programmability/examples: - crypto/sources/ecdsa.move - crypto/sources/groth16.move - games/sources/drand_lib.move - games/sources/drand_based_lottery.move - games/sources/drand_based_scratch_card.move - games/sources/vdf_based_lottery.move Modernising and cleaning them up in the process: - Applying wrapping consistently at 100 characters, and cleaning up comments. - Removing unnecessary use of `entry` functions, including returning values instead of transfering to sender in some cases. - Using receiver functions where possible. - Standardising file order and adding titles for sections. - Standardising use of doc comments vs regular comments. - Using clever errors. This marks the final set of examples to be moved out of sui-programmability, which will then be deleted. ## Test plan ``` sui-framework-tests$ cargo nextest run -- run_examples_move_unit_tests ```
- Loading branch information
Showing
14 changed files
with
1,399 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[package] | ||
name = "ecdsa_k1" | ||
version = "0.0.1" | ||
edition = "2024.beta" | ||
|
||
[dependencies] | ||
Sui = { local = "../../../../crates/sui-framework/packages/sui-framework" } | ||
|
||
[addresses] | ||
ecdsa_k1 = "0x0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
/// A basic ECDSA utility contract to do the following: | ||
/// | ||
/// 1) Hash a piece of data using Keccak256, output an object with hashed data. | ||
/// 2) Recover a Secp256k1 signature to its public key, output an | ||
/// object with the public key. | ||
/// 3) Verify a Secp256k1 signature, produce an event for whether it is verified. | ||
module ecdsa_k1::example { | ||
use sui::ecdsa_k1; | ||
use sui::event; | ||
|
||
// === Object Types === | ||
|
||
/// Object that holds the output data | ||
public struct Output has key, store { | ||
id: UID, | ||
value: vector<u8> | ||
} | ||
|
||
// == Event Types === | ||
|
||
/// Event on whether the signature is verified | ||
public struct VerifiedEvent has copy, drop { | ||
is_verified: bool, | ||
} | ||
|
||
// === Public Functions === | ||
|
||
/// Hash the data using Keccak256, output an object with the hash to recipient. | ||
public fun keccak256( | ||
data: vector<u8>, | ||
recipient: address, | ||
ctx: &mut TxContext, | ||
) { | ||
let hashed = Output { | ||
id: object::new(ctx), | ||
value: sui::hash::keccak256(&data), | ||
}; | ||
// Transfer an output data object holding the hashed data to the recipient. | ||
transfer::public_transfer(hashed, recipient) | ||
} | ||
|
||
/// Recover the public key using the signature and message, assuming the signature was produced | ||
/// over the Keccak256 hash of the message. Output an object with the recovered pubkey to | ||
/// recipient. | ||
public fun ecrecover( | ||
signature: vector<u8>, | ||
msg: vector<u8>, | ||
recipient: address, | ||
ctx: &mut TxContext, | ||
) { | ||
let pubkey = Output { | ||
id: object::new(ctx), | ||
value: ecdsa_k1::secp256k1_ecrecover(&signature, &msg, 0), | ||
}; | ||
// Transfer an output data object holding the pubkey to the recipient. | ||
transfer::public_transfer(pubkey, recipient) | ||
} | ||
|
||
/// Recover the Ethereum address using the signature and message, assuming the signature was | ||
/// produced over the Keccak256 hash of the message. Output an object with the recovered address | ||
/// to recipient. | ||
public fun ecrecover_to_eth_address( | ||
mut signature: vector<u8>, | ||
msg: vector<u8>, | ||
recipient: address, | ||
ctx: &mut TxContext, | ||
) { | ||
// Normalize the last byte of the signature to be 0 or 1. | ||
let v = &mut signature[64]; | ||
if (*v == 27) { | ||
*v = 0; | ||
} else if (*v == 28) { | ||
*v = 1; | ||
} else if (*v > 35) { | ||
*v = (*v - 1) % 2; | ||
}; | ||
|
||
// Ethereum signature is produced with Keccak256 hash of the message, so the last param is | ||
// 0. | ||
let pubkey = ecdsa_k1::secp256k1_ecrecover(&signature, &msg, 0); | ||
let uncompressed = ecdsa_k1::decompress_pubkey(&pubkey); | ||
|
||
// Take the last 64 bytes of the uncompressed pubkey. | ||
let mut uncompressed_64 = vector[]; | ||
let mut i = 1; | ||
while (i < 65) { | ||
uncompressed_64.push_back(uncompressed[i]); | ||
i = i + 1; | ||
}; | ||
|
||
// Take the last 20 bytes of the hash of the 64-bytes uncompressed pubkey. | ||
let hashed = sui::hash::keccak256(&uncompressed_64); | ||
let mut addr = vector[]; | ||
let mut i = 12; | ||
while (i < 32) { | ||
addr.push_back(hashed[i]); | ||
i = i + 1; | ||
}; | ||
|
||
let addr_object = Output { | ||
id: object::new(ctx), | ||
value: addr, | ||
}; | ||
|
||
// Transfer an output data object holding the address to the recipient. | ||
transfer::public_transfer(addr_object, recipient) | ||
} | ||
|
||
/// Verified the secp256k1 signature using public key and message assuming Keccak was using when | ||
/// signing. Emit an is_verified event of the verification result. | ||
public fun secp256k1_verify(signature: vector<u8>, public_key: vector<u8>, msg: vector<u8>) { | ||
event::emit(VerifiedEvent { | ||
is_verified: ecdsa_k1::secp256k1_verify(&signature, &public_key, &msg, 0) | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[package] | ||
name = "groth16" | ||
version = "0.0.1" | ||
edition = "2024.beta" | ||
|
||
[dependencies] | ||
Sui = { local = "../../../../crates/sui-framework/packages/sui-framework" } | ||
|
||
[addresses] | ||
groth16 = "0x0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
/// A verifier for the Groth16 zk-SNARK over the BLS12-381 construction. | ||
/// See https://eprint.iacr.org/2016/260.pdf for details. | ||
module groth16::example { | ||
use sui::bls12381; | ||
use sui::group_ops::Element; | ||
|
||
// === Types === | ||
|
||
/// A Groth16 proof. | ||
public struct Proof has drop { | ||
a: Element<bls12381::G1>, | ||
b: Element<bls12381::G2>, | ||
c: Element<bls12381::G1>, | ||
} | ||
|
||
/// A Groth16 verifying key used to verify a zero-knowledge proof. | ||
public struct VerifyingKey has store, drop { | ||
alpha: Element<bls12381::G1>, | ||
beta: Element<bls12381::G2>, | ||
gamma: Element<bls12381::G2>, | ||
gamma_abc: vector<Element<bls12381::G1>>, | ||
delta: Element<bls12381::G2>, | ||
} | ||
|
||
/// A prepared verifying key. This makes verification faster than using the verifying key directly. | ||
public struct PreparedVerifyingKey has store, drop { | ||
alpha_beta: Element<bls12381::GT>, | ||
gamma_neg: Element<bls12381::G2>, | ||
gamma_abc: vector<Element<bls12381::G1>>, | ||
delta_neg: Element<bls12381::G2>, | ||
} | ||
|
||
// === Errors === | ||
|
||
#[error] | ||
const EInvalidNumberOfPublicInputs: vector<u8> = | ||
b"There must be one more public input than gamma_abc entries in the verifying key."; | ||
|
||
// === Public Functions === | ||
|
||
/// Create a new `Proof`. | ||
public fun create_proof( | ||
a: Element<bls12381::G1>, | ||
b: Element<bls12381::G2>, | ||
c: Element<bls12381::G1>, | ||
): Proof { | ||
Proof { a, b, c } | ||
} | ||
|
||
/// Create a new `VerifyingKey`. | ||
public fun create_verifying_key( | ||
alpha: Element<bls12381::G1>, | ||
beta: Element<bls12381::G2>, | ||
gamma: Element<bls12381::G2>, | ||
gamma_abc: vector<Element<bls12381::G1>>, | ||
delta: Element<bls12381::G2>, | ||
): VerifyingKey { | ||
VerifyingKey { alpha, beta, gamma, gamma_abc, delta } | ||
} | ||
|
||
/// Create a PreparedVerifyingKey from a VerifyingKey. This only have to be | ||
/// done once. | ||
public fun prepare(vk: VerifyingKey): PreparedVerifyingKey { | ||
PreparedVerifyingKey { | ||
alpha_beta: bls12381::pairing(&vk.alpha, &vk.beta), | ||
gamma_neg: bls12381::g2_neg(&vk.gamma), | ||
gamma_abc: vk.gamma_abc, | ||
delta_neg: bls12381::g2_neg(&vk.delta), | ||
} | ||
} | ||
|
||
/// Verify a Groth16 proof with some public inputs and a verifying key. | ||
public fun verify( | ||
pvk: &PreparedVerifyingKey, | ||
proof: &Proof, | ||
public_inputs: &vector<Element<bls12381::Scalar>>, | ||
): bool { | ||
let prepared_inputs = prepare_inputs(&pvk.gamma_abc, public_inputs); | ||
let mut lhs = bls12381::pairing(&proof.a, &proof.b); | ||
lhs = bls12381::gt_add(&lhs, &bls12381::pairing(&prepared_inputs, &pvk.gamma_neg)); | ||
lhs = bls12381::gt_add(&lhs, &bls12381::pairing(&proof.c, &pvk.delta_neg)); | ||
lhs == pvk.alpha_beta | ||
} | ||
|
||
// === Helpers === | ||
|
||
fun prepare_inputs( | ||
vk_gamma_abc: &vector<Element<bls12381::G1>>, | ||
public_inputs: &vector<Element<bls12381::Scalar>>, | ||
): Element<bls12381::G1> { | ||
let length = public_inputs.length(); | ||
assert!(length + 1 == vk_gamma_abc.length(), EInvalidNumberOfPublicInputs); | ||
|
||
let mut output = vk_gamma_abc[0]; | ||
let mut i = 0; | ||
while (i < length) { | ||
output = bls12381::g1_add( | ||
&output, | ||
&bls12381::g1_mul(&public_inputs[i], &vk_gamma_abc[i + 1]), | ||
); | ||
i = i + 1; | ||
}; | ||
output | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#[test_only] | ||
module groth16::example_tests { | ||
use sui::bls12381; | ||
use groth16::example::{create_verifying_key, create_proof, verify}; | ||
|
||
#[test] | ||
fun test_verification() { | ||
let vk = create_verifying_key( | ||
bls12381::g1_from_bytes(&x"b58cfc3b0f43d98e7dbe865af692577d52813cb62ef3c355215ec3be2a0355a1ae5da76dd3e626f8a60de1f4a8138dee"), | ||
bls12381::g2_from_bytes(&x"9047b42915b32ef9dffe3acc0121a1450416e7f9791159f165ab0729d744da3ed82cd4822ca1d7fef35147cfd620b59b0ca09db7dff43aab6c71635ba8f86a83f058e9922e5cdacbe21d0e5e855cf1e776a61b272c12272fe526f5ba3b48d579"), | ||
bls12381::g2_from_bytes(&x"ad7c5a6cefcae53a3fbae72662c7c04a2f8e1892cb83615a02b32c31247172b7f317489b84e72f14acaf4f3e9ed18141157c6c1464bf15d957227f75a3c550d6d27f295b41a753340c6eec47b471b2cb8664c84f3e9b725325d3fb8afc6b56d0"), | ||
vector[ | ||
bls12381::g1_from_bytes(&x"b2c9c61ccc28e913284a47c34e60d487869ff423dd574db080d35844f9eddd2b2967141b588a35fa82a278ce39ae6b1a"), | ||
bls12381::g1_from_bytes(&x"9026ae12d58d203b4fc5dfad4968cbf51e43632ed1a05afdcb2e380ee552b036fbefc7780afe9675bcb60201a2421b2c") | ||
], | ||
bls12381::g2_from_bytes(&x"b1294927d02f8e86ac57c3b832f4ecf5e03230445a9a785ac8d25cf968f48cca8881d0c439c7e8870b66567cf611da0c1734316632f39d3125c8cecca76a8661db91cbfae217547ea1fc078a24a1a31555a46765011411094ec649d42914e2f5"), | ||
); | ||
|
||
let public_inputs = vector[ | ||
bls12381::scalar_from_bytes(&x"46722abc81a82d01ac89c138aa01a8223cb239ceb1f02cdaad7e1815eb997ca6") | ||
]; | ||
|
||
let proof = create_proof( | ||
bls12381::g1_from_bytes(&x"9913bdcabdff2cf1e7dea1673c5edba5ed6435df2f2a58d6d9e624609922bfa3976a98d991db333812bf6290a590afaa"), | ||
bls12381::g2_from_bytes(&x"b0265b35af5069593ee88626cf3ba9a0f07699510a25aec3a27048792ab83b3467d6b814d1c09c412c4dcd7656582e6607b72915081c82794ccedf643c27abace5b23a442079d8dcbd0d68dd697b8e0b699a1925a5f2c77f5237efbbbeda3bd0"), | ||
bls12381::g1_from_bytes(&x"b1237cf48ca7aa98507e826aac336b9e24f14133de1923fffac602a1203b795b3037c4c94e7246bacee7b2757ae912e5"), | ||
); | ||
|
||
assert!(vk.prepare().verify(&proof, &public_inputs)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[package] | ||
name = "DRAND" | ||
version = "0.0.1" | ||
edition = "2024.beta" | ||
|
||
[dependencies] | ||
Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } | ||
|
||
[addresses] | ||
drand = "0x0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
/// Helper module for working with drand outputs. | ||
/// | ||
/// Currently works with chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971 | ||
/// (quicknet). | ||
/// | ||
/// See examples of how to use this in lottery.move and scratch_card.move. | ||
/// | ||
/// If you want to use this module with the default network which has a 30s period, you need to | ||
/// change the public key, genesis time and include the previous signature in | ||
/// verify_drand_signature. See https://drand.love/developer/ or the previous version of this file: | ||
/// https://github.com/MystenLabs/sui/blob/92df778310679626f00bc4226d7f7a281322cfdd/sui_programmability/examples/games/sources/drand_lib.move | ||
module drand::lib { | ||
use std::hash::sha2_256; | ||
use sui::bls12381; | ||
|
||
// === Error Codes === | ||
|
||
#[error] | ||
const EInvalidRndLength: vector<u8> = | ||
b"Invalid randomness length."; | ||
|
||
#[error] | ||
const EInvalidProof: vector<u8> = | ||
b"Invalid proof of DRAND signature."; | ||
|
||
// === Constants === | ||
|
||
/// The genesis time of chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971. | ||
const GENESIS: u64 = 1692803367; | ||
|
||
/// The public key of chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971. | ||
const DRAND_PK: vector<u8> = | ||
x"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"; | ||
|
||
/// The time in seconds between randomness beacon rounds. | ||
const PERIOD: u64 = 3; | ||
|
||
// === Public Functions === | ||
|
||
/// Check that a given epoch time has passed by verifying a drand signature from a later time. | ||
/// round must be at least (epoch_time - GENESIS)/PERIOD + 1). | ||
public fun verify_time_has_passed(epoch_time: u64, sig: vector<u8>, round: u64) { | ||
assert!(epoch_time <= GENESIS + PERIOD * (round - 1), EInvalidProof); | ||
verify_drand_signature(sig, round); | ||
} | ||
|
||
/// Check a drand output. | ||
public fun verify_drand_signature(sig: vector<u8>, mut round: u64) { | ||
// Convert round to a byte array in big-endian order. | ||
let mut round_bytes: vector<u8> = vector[0, 0, 0, 0, 0, 0, 0, 0]; | ||
let mut i = 7; | ||
|
||
// Note that this loop never copies the last byte of round_bytes, though it is not expected to ever be non-zero. | ||
while (i > 0) { | ||
let curr_byte = round % 0x100; | ||
let curr_element = &mut round_bytes[i]; | ||
*curr_element = (curr_byte as u8); | ||
round = round >> 8; | ||
i = i - 1; | ||
}; | ||
|
||
// Compute sha256(prev_sig, round_bytes). | ||
let digest = sha2_256(round_bytes); | ||
// Verify the signature on the hash. | ||
let drand_pk = DRAND_PK; | ||
assert!(bls12381::bls12381_min_sig_verify(&sig, &drand_pk, &digest), EInvalidProof); | ||
} | ||
|
||
/// Derive a uniform vector from a drand signature. | ||
public fun derive_randomness(drand_sig: vector<u8>): vector<u8> { | ||
sha2_256(drand_sig) | ||
} | ||
|
||
// Converts the first 16 bytes of rnd to a u128 number and outputs its modulo with input n. | ||
// Since n is u64, the output is at most 2^{-64} biased assuming rnd is uniformly random. | ||
public fun safe_selection(n: u64, rnd: &vector<u8>): u64 { | ||
assert!(rnd.length() >= 16, EInvalidRndLength); | ||
let mut m: u128 = 0; | ||
let mut i = 0; | ||
while (i < 16) { | ||
m = m << 8; | ||
let curr_byte = rnd[i]; | ||
m = m + (curr_byte as u128); | ||
i = i + 1; | ||
}; | ||
let n_128 = (n as u128); | ||
let module_128 = m % n_128; | ||
let res = (module_128 as u64); | ||
res | ||
} | ||
} |
Oops, something went wrong.