Skip to content

Commit

Permalink
[Examples/Move] Port remaining Crypto Sui Programmability Examples
Browse files Browse the repository at this point in the history
## 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
amnn committed Jul 11, 2024
1 parent fdeba2a commit b36e2a6
Show file tree
Hide file tree
Showing 14 changed files with 1,399 additions and 0 deletions.
10 changes: 10 additions & 0 deletions examples/move/crypto/ecdsa_k1/Move.toml
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"
119 changes: 119 additions & 0 deletions examples/move/crypto/ecdsa_k1/sources/example.move
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)
});
}
}
10 changes: 10 additions & 0 deletions examples/move/crypto/groth16/Move.toml
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"
108 changes: 108 additions & 0 deletions examples/move/crypto/groth16/sources/example.move
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
}
}
34 changes: 34 additions & 0 deletions examples/move/crypto/groth16/tests/example_tests.move
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));
}
}
10 changes: 10 additions & 0 deletions examples/move/drand/Move.toml
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"
94 changes: 94 additions & 0 deletions examples/move/drand/sources/lib.move
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
}
}
Loading

0 comments on commit b36e2a6

Please sign in to comment.