Skip to content

Commit

Permalink
Support EOS (#13)
Browse files Browse the repository at this point in the history
* Support EOS

* Add eos document
  • Loading branch information
joii2020 committed Oct 24, 2023
1 parent 3303d5f commit 9daf7ff
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 87 deletions.
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

0 comments on commit 9daf7ff

Please sign in to comment.