Skip to content

Commit

Permalink
Merge pull request #52 from pnetwork-association/fix/eos-onchain-even…
Browse files Browse the repository at this point in the history
…t-parsing

Add EOS on chain event parsing (3/4)
  • Loading branch information
gitmp01 authored Nov 6, 2024
2 parents e9ce187 + b47f775 commit 5999374
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 32 deletions.
42 changes: 35 additions & 7 deletions cpp/contracts/pam.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace eosio {
using bytes = std::vector<uint8_t>;
namespace pam {
// aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906
const bytes CHAIN_ID = {
0xac, 0xa3, 0x76, 0xf2, 0x06, 0xb8, 0xfc, 0x25,
0xa6, 0xed, 0x44, 0xdb, 0xdc, 0x66, 0x54, 0x7c,
Expand Down Expand Up @@ -65,6 +66,10 @@ namespace eosio {
}

void check_authorization(name adapter, const operation& operation, const metadata& metadata, checksum256& event_id) {
// Metadata preimage format:
// | version | protocol | origin | blockHash | txHash | eventPayload |
// | 1B | 1B | 32B | 32B | 32B | varlen |
// +----------- context ---------+------------- event ---------------+
check(context_checks(operation, metadata), "unexpected context");

tee_pubkey _tee_pubkey(adapter, adapter.value);
Expand All @@ -84,6 +89,9 @@ namespace eosio {
public_key recovered_pubkey = recover_key(event_id, sig);
check(recovered_pubkey == tee_key, "invalid signature");

// Event payload format
// | emitter | topic-0 | topics-1 | topics-2 | topics-3 | eventBytes |
// | 32B | 32B | 32B | 32B | 32B | varlen |
offset = 0;
bytes event_payload(metadata.preimage.begin() + 98, metadata.preimage.end());
bytes emitter = extract_32bytes(event_payload, offset);
Expand All @@ -94,39 +102,59 @@ namespace eosio {
check(topic_zero == exp_topic_zero && !is_all_zeros(topic_zero), "unexpected topic zero");
offset += 32 * 4; // skip other topics

// check nonce
bytes event_data(event_payload.begin() + offset, event_payload.end());
// Checking the protocol id against 0x02 (EOS chains)
// If the condition is satified we expect data content to be
// a JSON string like:
//
// '{"event_bytes":"00112233445566"}'
//
// in hex would be
//
// 7b226576656e745f6279746573223a223030313132323333343435353636227d
//
// We want to extract 00112233445566, so this is performed by skipping
// the first 16 chars and the trailing 2 chars
uint8_t protocol_id = metadata.preimage[1];
auto start = protocol_id == 2 // EOSIO protocol
? event_payload.begin() + offset + 16
: event_payload.begin() + offset;

auto end = protocol_id == 2
? event_payload.end() - 2
: event_payload.end();

bytes raw_data(start, end);

bytes event_data = protocol_id == 2
? from_utf8_encoded_to_bytes(raw_data)
: raw_data;

offset = 0;
bytes nonce = extract_32bytes(event_data, offset);
uint64_t nonce_int = bytes32_to_uint64(nonce);

check(operation.nonce == nonce_int, "nonce do not match");
offset += 32;

// check origin token
bytes token = extract_32bytes(event_data, offset);
checksum256 token_hash = bytes32_to_checksum256(token);
check(operation.token == token_hash, "token address do not match");
offset += 32;

// check destination chain id
bytes dest_chain_id = extract_32bytes(event_data, offset);
check(operation.destinationChainId == dest_chain_id, "destination chain id does not match with the expected one");
check(CHAIN_ID == dest_chain_id, "destination chain id does not match with the current chain");
offset += 32;

// check amount
bytes amount = extract_32bytes(event_data, offset);
uint128_t amount_num = bytes32_to_uint128(amount);
check(to_wei(operation.amount) == amount_num, "amount do not match");
offset += 32;

// check sender address
bytes sender = extract_32bytes(event_data, offset);
check(operation.sender == sender, "sender do not match");
offset += 32;

// check recipient address
bytes recipient_len = extract_32bytes(event_data, offset);
offset += 32;
uint128_t recipient_len_num = bytes32_to_uint128(recipient_len);
Expand Down
32 changes: 30 additions & 2 deletions cpp/contracts/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ namespace eosio {
}

bytes extract_32bytes(const bytes& data, uint128_t offset) {
bytes _data(data.begin() + offset, data.begin() + offset + 32);
return _data;
bytes _data(data.begin() + offset, data.begin() + offset + 32);
return _data;
}

signature convert_bytes_to_signature(const bytes& input_bytes) {
Expand Down Expand Up @@ -214,4 +214,32 @@ namespace eosio {
name name_value(name_str);
return name_value;
}

uint8_t from_hex_char_to_uint8(uint8_t x) {
if ((x >= 97) && (x <= 102)) { // [a, b, c, ..., f]
x -= 87;
} else if ((x >= 65) && (x <= 70)) { // [A, B, C, ..., F]
x -= 55;
} else if ((x >= 48) && (x <= 57)) { // [0, 1, 2, ... ,9]
x -= 48;
}

return x;
}

bytes from_utf8_encoded_to_bytes(const bytes &utf8_encoded) {
check(utf8_encoded.size() % 2 == 0, "invalid utf-8 encoded string");

bytes x(utf8_encoded.size() / 2, 0); // fill it with zeros

uint64_t k = 0;
uint8_t b1, b2;
for (uint64_t i = 0; i < utf8_encoded.size(); i += 2) {
b1 = from_hex_char_to_uint8(utf8_encoded[i]);
b2 = from_hex_char_to_uint8(utf8_encoded[i + 1]);
x[k++] = b1 * 16 + b2;
}

return x;
}
}
74 changes: 73 additions & 1 deletion cpp/test/pam.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ describe('PAM testing', () => {

const attestation = []
const blockchain = new Blockchain()
const eosChainId =
'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906'
const operation = getOperationSample({
amount: '1337.0000 TKN',
sender: '0000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf',
token: '000000000000000000000000f2e246bb76df876cef8b38ae84130f4f55de395b',
chainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
chainId: eosChainId,
recipient,
})
const data =
Expand Down Expand Up @@ -314,5 +316,75 @@ describe('PAM testing', () => {
expect(pam.contract.bc.console).to.be.equal(expectedEventId)
})
})

it('Should authorize an EOSIO operation successfully', async () => {
const blockId =
'179ed57f474f446f2c9f6ea6702724cdad0cf26422299b368755ed93c0134a35'
const txId =
'27598a45ee610287d85695f823f8992c10602ce5bf3240ee20635219de4f734f'
const nonce = 0
const token =
'0000000000000000000000000000000000000000000000746b6e2e746f6b656e'
const originChainId = no0x(Chains(Protocols.Eos).Jungle)
const destinationChainId = eosChainId
const amount = '9.9825 TKN'
const sender =
'0000000000000000000000000000000000000000000000000000000075736572'
const recipient = 'recipient'
const data = ''
const operation2 = getOperationSample({
blockId,
txId,
nonce,
token,
originChainId,
destinationChainId,
amount,
sender,
recipient,
data,
})

const eosEmitter = Buffer.from('adapter')
.toString('hex')
.padStart(64, '0')
const eosTopic0 = Buffer.from('swap').toString('hex').padStart(64, '0')

const ea2 = new ProofcastEventAttestator({
version: Versions.V1,
protocolId: Protocols.Eos,
chainId: Chains(Protocols.Eos).Jungle,
privateKey,
})

const eventData = {
event_bytes:
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746b6e2e746f6b656eaca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e9060000000000000000000000000000000000000000000000008a88f6dc4656400000000000000000000000000000000000000000000000000000000000757365720000000000000000000000000000000000000000000000000000000000000009726563697069656e74',
}

const event2 = {
blockHash: operation2.blockId,
transactionHash: operation2.txId,
account: 'adapter',
action: 'swap',
data: eventData,
}

await adapter.contract.actions
.setorigin([operation2.originChainId, eosEmitter, eosTopic0])
.send(active(adapter.account))

const metadata2 = getMetadataSample({
signature: no0x(ea2.formatEosSignature(ea2.sign(event2))),
preimage: no0x(ea2.getEventPreImage(event2)),
})

await pam.contract.actions
.isauthorized([operation2, metadata2])
.send(active(user))
expect(pam.contract.bc.console).to.be.equal(
'42cde5d898147a7bd21006e0fe541092151262cb2bde3a3244587e7993c473e0',
)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class ProofcastEventAttestator {
return concat([
zeroPadValue(Buffer.from(event.account, 'utf-8'), 32),
...topics,
event.data,
Buffer.from(JSON.stringify(event.data), 'utf-8'),
])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ describe('Proofcast Event Attestator Tests', () => {

// We are going to extract this = require( the a subfield of the)
// official data
const data =
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746b6e2e746f6b656e00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000008a88f6dc465640000000000000000000000000000000000000000000000000000000000075736572000000000000000000000000000000000000000000000000000000000000002a307836386262656436613437313934656666316366353134623530656139313839353539376663393165'
const data = {
event_bytes:
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746b6e2e746f6b656e00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000008a88f6dc465640000000000000000000000000000000000000000000000000000000000075736572000000000000000000000000000000000000000000000000000000000000002a307836386262656436613437313934656666316366353134623530656139313839353539376663393165',
}

const ea = new ProofcastEventAttestator({
version: Versions.V1,
Expand All @@ -73,7 +75,7 @@ describe('Proofcast Event Attestator Tests', () => {
}

const expectedSignature =
'0x1cfc81a6dc16147e5d82d3b9c2fef1ef2125403b92f328439da20ddd5903aef1276adefaeaca2579a342b00149c6916f93d988f81514ab535ebf19dd4eebed3519'
'0x1b546cb297b24aab5b445756f1d0beece3dad851d2cbd8d973f89f69e83f82b77016c87be815fa95bf25d37fb10c3f884cb200d38495e2d1c1bb686e9de38842a5'

expect(ea.formatEosSignature(ea.sign(event))).toStrictEqual(
expectedSignature,
Expand Down
71 changes: 53 additions & 18 deletions solidity/src/contracts/PAM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ contract PAM is Ownable, IPAM {

if (ECDSA.recover(eventId, metadata.signature) != teeAddress)
return (false, eventId);

// Event payload format
// | emitter | topic-0 | topics-1 | topics-2 | topics-3 | eventBytes |
// | 32B | 32B | 32B | 32B | 32B | varlen |
Expand All @@ -110,7 +109,28 @@ contract PAM is Ownable, IPAM {

offset += 32 * 3; // skip other topics

if (!this.doesContentMatchOperation(eventPayload[offset:], operation))
// Checking the protocol id against 0x02 (EOS chains)
// If the condition is satified we expect data content to be
// a JSON string like:
//
// '{"event_bytes":"00112233445566"}'
//
// in hex would be
//
// 7b226576656e745f6279746573223a223030313132323333343435353636227d
//
// We want to extract 00112233445566, so this is performed by skipping
// the first 16 chars and the trailing 2 chars
//
// Can't factor out these into variables because otherwise it would
// raise the "stack too deep" error
bytes memory eventBytes = uint8(metadata.preimage[1]) == 0x02
? _fromUTF8EncodedToBytes(
eventPayload[(offset + 16):(eventPayload.length - 2)]
)
: eventPayload[offset:];

if (!this.doesContentMatchOperation(eventBytes, operation))
return (false, eventId);

return (true, eventId);
Expand Down Expand Up @@ -150,6 +170,7 @@ contract PAM is Ownable, IPAM {
Metadata calldata metadata
) internal pure returns (bool) {
uint16 offset = 2; // skip protocol, version

bytes32 originChainId = bytes32(metadata.preimage[offset:offset += 32]);

if (originChainId != operation.originChainId) return false;
Expand All @@ -163,28 +184,42 @@ contract PAM is Ownable, IPAM {
return true;
}

function _fromHexCharToUint8(uint8 x) internal pure returns (uint8) {
if ((x >= 97) && (x <= 102)) {
x -= 87;
} else if ((x >= 65) && (x <= 70)) {
x -= 55;
} else if ((x >= 48) && (x <= 57)) {
x -= 48;
}
return x;
}

function _fromUTF8EncodedToBytes(
bytes memory utf8Encoded
) internal pure returns (bytes memory) {
require(utf8Encoded.length % 2 == 0, "invalid utf-8 encoded string");
bytes memory x = new bytes(utf8Encoded.length / 2);

uint k;
uint8 b1;
uint8 b2;
for (uint i = 0; i < utf8Encoded.length; i += 2) {
b1 = _fromHexCharToUint8(uint8(utf8Encoded[i]));
b2 = _fromHexCharToUint8(uint8(utf8Encoded[i + 1]));
x[k++] = bytes1(b1 * 16 + b2);
}
return x;
}

function _bytesToAddress(bytes memory tmp) internal pure returns (address) {
uint160 iaddr = 0;
uint160 b1;
uint160 b2;
for (uint256 i = 2; i < 2 + 2 * 20; i += 2) {
iaddr *= 256;
b1 = uint160(uint8(tmp[i]));
b2 = uint160(uint8(tmp[i + 1]));
if ((b1 >= 97) && (b1 <= 102)) {
b1 -= 87;
} else if ((b1 >= 65) && (b1 <= 70)) {
b1 -= 55;
} else if ((b1 >= 48) && (b1 <= 57)) {
b1 -= 48;
}
if ((b2 >= 97) && (b2 <= 102)) {
b2 -= 87;
} else if ((b2 >= 65) && (b2 <= 70)) {
b2 -= 55;
} else if ((b2 >= 48) && (b2 <= 57)) {
b2 -= 48;
}
b1 = uint160(_fromHexCharToUint8(uint8(tmp[i])));
b2 = uint160(_fromHexCharToUint8(uint8(tmp[i + 1])));
iaddr += (b1 * 16 + b2);
}
return address(iaddr);
Expand Down
Loading

0 comments on commit 5999374

Please sign in to comment.