Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support EOS #13

Merged
merged 5 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/clang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:
run: make -f examples/auth-demo/Makefile.clang all
- name: Run auth_rust tests
run: cd tests/auth_rust && cargo test
- name: Clean auth_rust
run: rm -rf tests/auth_rust/target
- name: Install ckb-debugger
run: cd tests/auth_spawn_rust && make install
- name: Run auth_spawn_rust tests
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:
run: make all-via-docker
- name: Run auth_rust tests
run: cd tests/auth_rust && bash run.sh
- name: Clean auth_rust
run: rm -rf tests/auth_rust/target
- name: Install ckb-debugger
run: cd tests/auth_spawn_rust && make install
- name: Run auth_spawn_rust tests
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The following blockchains are supported:

* [Bitcoin](./docs/bitcoin.md)
* [Ethereum](./docs/ethereum.md)
* EOS
* [EOS](./docs/eos.md)
* Tron
* Dogecoin
* CKB
Expand Down
42 changes: 23 additions & 19 deletions c/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,28 @@ int validate_signature_eth(void *prefilled_data, const uint8_t *sig,
return ret;
}

int validate_signature_eos(void *prefilled_data, const uint8_t *sig, size_t sig_len, const uint8_t *msg, size_t msg_len,
uint8_t *output, size_t *output_len) {
int err = 0;
if (*output_len < BLAKE160_SIZE) {
return SECP256K1_PUBKEY_SIZE;
}
uint8_t out_pubkey[UNCOMPRESSED_SECP256K1_PUBKEY_SIZE];
size_t out_pubkey_size = UNCOMPRESSED_SECP256K1_PUBKEY_SIZE;
err = _recover_secp256k1_pubkey_btc(sig, sig_len, msg, msg_len, out_pubkey, &out_pubkey_size, false);
CHECK(err);

blake2b_state ctx;
blake2b_init(&ctx, BLAKE2B_BLOCK_SIZE);
blake2b_update(&ctx, out_pubkey, out_pubkey_size);
blake2b_final(&ctx, out_pubkey, BLAKE2B_BLOCK_SIZE);

memcpy(output, out_pubkey, BLAKE160_SIZE);
*output_len = BLAKE160_SIZE;
exit:
return err;
}

int validate_signature_btc(void *prefilled_data, const uint8_t *sig,
size_t sig_len, const uint8_t *msg, size_t msg_len,
uint8_t *output, size_t *output_len) {
Expand Down Expand Up @@ -651,24 +673,6 @@ static void split_hex_hash(const uint8_t *source, unsigned char *dest) {
}
}

int convert_eos_message(const uint8_t *msg, size_t msg_len, uint8_t *new_msg,
size_t new_msg_len) {
int err = 0;
if (msg_len != new_msg_len || msg_len != BLAKE2B_BLOCK_SIZE)
return ERROR_INVALID_ARG;
int split_message_len = BLAKE2B_BLOCK_SIZE * 2 + 5;
unsigned char splited_message[split_message_len];
/* split message to words length <= 12 */
split_hex_hash(msg, splited_message);

const mbedtls_md_info_t *md_info =
mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);

err = md_string(md_info, msg, msg_len, new_msg);
if (err != 0) return err;
return 0;
}

#define MESSAGE_HEX_LEN 64
int convert_btc_message_variant(const uint8_t *msg, size_t msg_len,
uint8_t *new_msg, size_t new_msg_len,
Expand Down Expand Up @@ -964,7 +968,7 @@ __attribute__((visibility("default"))) int ckb_auth_validate(
} else if (auth_algorithm_id == AuthAlgorithmIdEos) {
CHECK2(signature_size == SECP256K1_SIGNATURE_SIZE, ERROR_INVALID_ARG);
err = verify(pubkey_hash, signature, signature_size, message,
message_size, validate_signature_eth, convert_eos_message);
message_size, validate_signature_eos, convert_copy);
CHECK(err);
} else if (auth_algorithm_id == AuthAlgorithmIdTron) {
CHECK2(signature_size == SECP256K1_SIGNATURE_SIZE, ERROR_INVALID_ARG);
Expand Down
130 changes: 130 additions & 0 deletions docs/eos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# [EOS](../README.md)

In this guide, we will explore how to test `ckb-auth` using the official EOS tool: `cleos`.

## Quick Start

### Installing EOS

To get started, we recommend using precompiled binary files. You can find the official installation tutorial [here](https://developers.eos.io/manuals/eos/latest/install/install-prebuilt-binaries). Please keep in mind the following:

- Support is available only for x86 CPUs.
- It's advisable to use the officially recommended systems.
- In this document, we will focus on using the `cleos` binary.

### Creating Key Pairs

One of the advantages of `cleos` is that it can directly generate a key pair for signing. Here's how you can do it:

```bash
cleos create key --to-console
```

This command will produce output like this:
```text
Private key: 5K97VWAvvY7BGqojUwTkZ279EDfCXzae9DoArmw1DCcDHXwqpgp
Public key: EOS8Mizk2hTcnU8t3hpYErmNuWmptstbsmr3gGUeQY9swEw2AxeyU
```

### Signing Transactions

When using `cleos` for signing, you'll need three parameters: a private key, a chain ID, and the transaction data. Here's an example of how to sign a transaction:

```bash
cleos sign -k 5K97VWAvvY7BGqojUwTkZ279EDfCXzae9DoArmw1DCcDHXwqpgp \
-c 00112233445566778899aabbccddeeff00000000000000000000000000000000 \
"{ \"context_free_data\": [\"\00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff\"] }"
```

Output:
```text
{
"expiration": "1970-01-01T00:00:00",
"ref_block_num": 0,
"ref_block_prefix": 0,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [],
"transaction_extensions": [],
"signatures": [
"SIG_K1_KVot8AfLZKPiuwBZKxgco4pKCCfedjtrzyJij6iTmNfkq7Pw4HgizKNBCaXCMs8TNWFUg92g653LEW5GJyS1YFJw7Ciqns"
],
"context_free_data": [
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
]
}
```

In the given command, `-c` (Chain ID) is a 32-byte binary data that can be acquired from `nodeos`, the core service daemon running on every EOSIO node. Alternatively, it can be entered manually. When entering it manually, if it's too long, only the beginning is used; if it's too short, it will be padded with 0s. (Please note that while cleos may perform some corrections to the chain ID, `ckb-auth-cli` does not.)

A transaction is presented in JSON format and contains all the essential information for the transaction. `cleos` allows the use of `{}` to represent an empty JSON as a parameter. In this context, the `context_free_data` field is used to store the CKB sign message, enabling its inclusion in the signature. It's important to note that `cleos` only supports the use of double quotation marks (") when parsing JSON; single quotation marks (') should not be used.

After successful execution, you will receive a JSON data structure with the signature stored in the "signatures" field.

### Verifying Signatures

You can verify the generated signature using the `cleos validate signatures` command. Here's how you can do it:

```bash
cleos validate signatures \
-c 00112233445566778899aabbccddeeff00000000000000000000000000000000 \
"{ \"signatures\": \
[ \"SIG_K1_KVot8AfLZKPiuwBZKxgco4pKCCfedjtrzyJij6iTmNfkq7Pw4HgizKNBCaXCMs8TNWFUg92g653LEW5GJyS1YFJw7Ciqns\" ], \
\"context_free_data\": \
[ \"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff\" ] }"
```

Output:
```text
[
"EOS8Mizk2hTcnU8t3hpYErmNuWmptstbsmr3gGUeQY9swEw2AxeyU"
]
```

This command will output the public key of the signature, which you can manually compare to the one generated earlier.

To complete the verification process, you can also use `ckb-auth-cli`:

```shell
ckb-auth-cli eos verify \
--pubkey EOS8Mizk2hTcnU8t3hpYErmNuWmptstbsmr3gGUeQY9swEw2AxeyU \
--signature SIG_K1_KVot8AfLZKPiuwBZKxgco4pKCCfedjtrzyJij6iTmNfkq7Pw4HgizKNBCaXCMs8TNWFUg92g653LEW5GJyS1YFJw7Ciqns \
--chain_id 00112233445566778899aabbccddeeff00000000000000000000000000000000 \
--message 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
```

If successful, it will return "Success."

## EOS Transaction Details

### Public Key

In EOS transactions, public keys are used directly instead of an address. There's no need for conversion. An EOS public key is a text string, such as `EOS8Mizk2hTcnU8t3hpYErmNuWmptstbsmr3gGUeQY9swEw2AxeyU`. It consists of three parts:

- The prefix for the public key is typically "EOS," although it's possible for this prefix to be different, such as "PUB_K1." For more details on this, please refer to the code [here](https://github.com/EOSIO/fc/blob/863dc8d371fd4da25f89cb08b13737f009a9cec7/src/crypto/public_key.cpp#L77). However, in ckb-auth-cli, only "EOS" is supported as the prefix.

- The text following the prefix can be decoded using the default Base58 decoding method. After decoding, it results in a 37-byte binary data. The first 33 bytes of this data represent the actual public key, and the last 4 bytes are used for checksum purposes to verify the integrity of the public key.

- During the verification process, the public key is hashed using `Ripemd160`, and the first 4 bytes of the resulting data are compared to validate its authenticity.

Because EOS doesn't have addresses, and CKB-auth's `pubkeyhash` can only store 20 bytes, a similar signing method to CKB is applied to the public key. It's hashed using Blake2b-256, and the first 20 bytes of the resulting hash serve as the "public key hash" for CKB-auth.

### Signing and Verification

The provided information explains that `cleos` offers a "sign" subcommand for signing transactions. This signing process requires a private key, the chain ID, and the transaction as its inputs. You can find more details in the [official documentation](https://developers.eos.io/welcome/v2.1/protocol-guides/transactions_protocol).

The chain ID identifies the specific EOSIO blockchain and consists of a hash of its genesis state, which depends on the blockchain’s initial configuration parameters. In `cleos`, if you do not specify the chain ID, it will be obtained from `nodeos`. `nodeos` is the core service daemon that operates on every EOSIO node and plays a central role in managing the blockchain. This automatic retrieval of the chain ID from `nodeos` simplifies the process of signing transactions by ensuring the correct chain ID is used for the specific blockchain you are interacting with.

If the `chain-id` is not detected in `cleos`, it will be obtained through `nodeos`. (`nodeos` is the core service daemon that runs on every EOSIO node; you can refer to the [documentation](https://developers.eos.io/manuals/eos/latest/nodeos/index) for more information).

The provided information also explains that in the transaction, the signature is based on the data in the `context_free_data` field of the JSON. This field is converted to hexadecimal in `cleos`, and the CKB sign message is placed in this field. It's important to note that, in practice, a fixed value can be used here, such as filling it with `0` or using the mainnet's ID. For more technical details, you can refer to [this source](https://github.com/EOSIO/eos/blob/master/libraries/chain/transaction.cpp#L47).

After the signing process is completed, a JSON response is returned, from which the signature data can be extracted:

```
SIG_K1_KVot8AfLZKPiuwBZKxgco4pKCCfedjtrzyJij6iTmNfkq7Pw4HgizKNBCaXCMs8TNWFUg92g653LEW5GJyS1YFJw7Ciqns
```

This string is similar to a public key, with a prefix indicating its purpose, and "K1" signifying that it's using a K1 curve. The following data is still encoded in Base58. When decoded, the first 65 bytes represent the actual signature data, followed by a 4-character checksum.
60 changes: 34 additions & 26 deletions tests/auth_rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ pub fn calculate_sha256(buf: &[u8]) -> [u8; 32] {
c.finalize().into()
}

pub fn calculate_ripemd160(buf: &[u8]) -> [u8; 20] {
use mbedtls::hash::*;
let mut md = Md::new(Type::Ripemd).unwrap();
md.update(buf).expect("hash ripemd update");
let mut out = [0u8; 20];
md.finish(&mut out).expect("hash ripemd finish");

out
}

#[derive(Clone, Copy)]
pub enum AlgorithmType {
Ckb = 0,
Expand Down Expand Up @@ -892,30 +902,41 @@ impl Auth for EthereumAuth {

#[derive(Clone)]
pub struct EosAuth {
pub privkey: secp256k1::SecretKey,
pub pubkey: secp256k1::PublicKey,
pub privkey: Privkey,
pub compress: bool,
}
impl EosAuth {
fn new() -> Box<dyn Auth> {
let generator: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
let mut rng = thread_rng();
let (privkey, pubkey) = generator.generate_keypair(&mut rng);
Box::new(EosAuth { privkey, pubkey })
let privkey = Generator::random_privkey();
Box::new(BitcoinAuth {
privkey,
compress: true,
})
}
}
impl Auth for EosAuth {
fn get_pub_key_hash(&self) -> Vec<u8> {
EthereumAuth::get_eth_pub_key_hash(&self.pubkey)
let pub_key = self.privkey.pubkey().expect("pubkey");
let pub_key_vec: Vec<u8>;
if self.compress {
pub_key_vec = pub_key.serialize();
} else {
let mut temp: BytesMut = BytesMut::with_capacity(65);
temp.put_u8(4);
temp.put(Bytes::from(pub_key.as_bytes().to_vec()));
pub_key_vec = temp.freeze().to_vec();
}

ckb_hash::blake2b_256(pub_key_vec)[..20].to_vec()
}
fn get_algorithm_type(&self) -> u8 {
AlgorithmType::Eos as u8
}
fn convert_message(&self, message: &[u8; 32]) -> H256 {
let msg = calculate_sha256(message);
H256::from(msg)
H256::from(message.clone())
}
fn sign(&self, msg: &H256) -> Bytes {
EthereumAuth::eth_sign(msg, &self.privkey)
BitcoinAuth::btc_sign(msg, &self.privkey, self.compress)
}
}

Expand Down Expand Up @@ -966,8 +987,6 @@ impl BitcoinAuth {
})
}
pub fn get_btc_pub_key_hash(privkey: &Privkey, compress: bool) -> Vec<u8> {
use mbedtls::hash::{Md, Type};

let pub_key = privkey.pubkey().expect("pubkey");
let pub_key_vec: Vec<u8>;
if compress {
Expand All @@ -981,8 +1000,7 @@ impl BitcoinAuth {

let pub_hash = calculate_sha256(&pub_key_vec);

let mut msg = [0u8; 20];
Md::hash(Type::Ripemd, &pub_hash, &mut msg).expect("hash ripemd");
let msg = calculate_ripemd160(&pub_hash);
msg.to_vec()
}
pub fn btc_convert_message(message: &[u8; 32]) -> H256 {
Expand Down Expand Up @@ -1599,16 +1617,6 @@ impl RippleAuth {
})
}

fn hash_ripemd160(data: &[u8]) -> [u8; 20] {
use mbedtls::hash::*;
let mut md = Md::new(Type::Ripemd).unwrap();
md.update(data).expect("hash ripemd update");
let mut out = [0u8; 20];
md.finish(&mut out).expect("hash ripemd finish");

out
}

fn hash_sha256(data: &[u8]) -> [u8; 32] {
use mbedtls::hash::*;
let mut md = Md::new(Type::Sha256).unwrap();
Expand Down Expand Up @@ -1638,7 +1646,7 @@ impl RippleAuth {

pub fn hex_to_address(data: &[u8]) -> String {
let data = Self::hash_sha256(data);
let data: [u8; 20] = Self::hash_ripemd160(&data);
let data: [u8; 20] = calculate_ripemd160(&data);

let mut data = {
let mut buf = vec![0u8];
Expand All @@ -1652,7 +1660,7 @@ impl RippleAuth {
}

fn get_hash(data: &[u8]) -> [u8; 20] {
Self::hash_ripemd160(&Self::hash_sha256(data))
calculate_ripemd160(&Self::hash_sha256(data))
}

fn generate_tx(ckb_sign_msg: &[u8], pubkey: &[u8], sign: Option<&[u8]>) -> Vec<u8> {
Expand Down
41 changes: 0 additions & 41 deletions tests/auth_rust/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,47 +355,6 @@ fn convert_eth_error() {
);
}

#[test]
fn convert_eos_error() {
#[derive(Clone)]
struct EthConverFaileAuth(EosAuth);
impl Auth for EthConverFaileAuth {
fn get_pub_key_hash(&self) -> Vec<u8> {
EthereumAuth::get_eth_pub_key_hash(&self.0.pubkey)
}
fn get_algorithm_type(&self) -> u8 {
AlgorithmType::Eos as u8
}
fn convert_message(&self, message: &[u8; 32]) -> H256 {
use mbedtls::hash::{Md, Type::Sha256};
let mut md = Md::new(Sha256).unwrap();
md.update(message).expect("sha256 update data");
md.update(&[1, 2, 3]).expect("sha256 update data");

let mut msg = [0u8; 32];
md.finish(&mut msg).expect("sha256 finish");
H256::from(msg)
}
fn sign(&self, msg: &H256) -> Bytes {
EthereumAuth::eth_sign(msg, &self.0.privkey)
}
}

let generator: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
let mut rng = thread_rng();
let (privkey, pubkey) = generator.generate_keypair(&mut rng);

let auth: Box<dyn Auth> = Box::new(EthConverFaileAuth {
0: EosAuth { privkey, pubkey },
});
let config = TestConfig::new(&auth, EntryCategoryType::DynamicLinking, 1);
assert_result_error(
verify_unit(&config),
"failed conver eos",
&[AuthErrorCodeType::Mismatched as i32],
);
}

#[test]
fn convert_tron_error() {
#[derive(Clone)]
Expand Down
Loading