Browse Source

functions ripemd160(), sha256(), and keccak256() for Solana

ripemd160 has its own implementation, and sha256 uses a syscall. The
keccak256() function relies on sol_keccak256() syscall which is not
available yet.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 years ago
parent
commit
21ed60059a

+ 17 - 0
integration/solana/hash_functions.sol

@@ -0,0 +1,17 @@
+
+contract hash_functions {
+	function hash_ripemd160(bytes bs) public returns (bytes20) {
+		return ripemd160(bs);
+	}
+
+/* this relies on Solana providing the sol_keccak256() syscall, which is
+ * not available yet
+	function hash_kecccak256(bytes bs) public returns (bytes32) {
+		return keccak256(bs);
+	}
+*/
+
+	function hash_sha256(bytes bs) public returns (bytes32) {
+		return sha256(bs);
+	}
+}

+ 26 - 0
integration/solana/hash_functions.spec.ts

@@ -0,0 +1,26 @@
+import expect from 'expect';
+import { establishConnection } from './index';
+import crypto from 'crypto';
+
+describe('Deploy solang contract and test', () => {
+    it('hash function', async function () {
+        this.timeout(50000);
+
+        let conn = await establishConnection();
+
+        let hash_functions = await conn.loadProgram("hash_functions.so", "hash_functions.abi");
+
+        // call the constructor
+        await hash_functions.call_constructor(conn, []);
+
+        console.log("calling ripemd160");
+        let res = await hash_functions.call_function(conn, "hash_ripemd160", ['0x' + Buffer.from('Call me Ishmael.', 'utf8').toString('hex')]);
+
+        expect(res["0"]).toBe("0x0c8b641c461e3c7abbdabd7f12a8905ee480dadf");
+
+        console.log("calling sha256");
+        res = await hash_functions.call_function(conn, "hash_sha256", ['0x' + Buffer.from('Call me Ishmael.', 'utf8').toString('hex')]);
+
+        expect(res["0"]).toBe("0x458f3ceeeec730139693560ecf66c9c22d9c7bc7dcb0599e8e10b667dfeac043");
+    });
+});

+ 2 - 1
src/emit/mod.rs

@@ -6405,11 +6405,12 @@ impl<'a> Contract<'a> {
     }
 }
 
-static BPF_IR: [&[u8]; 4] = [
+static BPF_IR: [&[u8]; 5] = [
     include_bytes!("../../stdlib/bpf/stdlib.bc"),
     include_bytes!("../../stdlib/bpf/bigint.bc"),
     include_bytes!("../../stdlib/bpf/format.bc"),
     include_bytes!("../../stdlib/bpf/solana.bc"),
+    include_bytes!("../../stdlib/bpf/ripemd160.bc"),
 ];
 
 static WASM_IR: [&[u8]; 4] = [

+ 107 - 5
src/emit/solana.rs

@@ -89,6 +89,11 @@ impl SolanaTarget {
         let void_ty = contract.context.void_type();
         let u8_ptr = contract.context.i8_type().ptr_type(AddressSpace::Generic);
         let u64_ty = contract.context.i64_type();
+        let u32_ty = contract.context.i32_type();
+        let sol_bytes = contract
+            .context
+            .struct_type(&[u8_ptr.into(), u64_ty.into()], false)
+            .ptr_type(AddressSpace::Generic);
 
         let function = contract.module.add_function(
             "sol_alloc_free_",
@@ -107,6 +112,24 @@ impl SolanaTarget {
         function
             .as_global_value()
             .set_unnamed_address(UnnamedAddress::Local);
+
+        let function = contract.module.add_function(
+            "sol_sha256",
+            void_ty.fn_type(&[sol_bytes.into(), u32_ty.into(), u8_ptr.into()], false),
+            None,
+        );
+        function
+            .as_global_value()
+            .set_unnamed_address(UnnamedAddress::Local);
+
+        let function = contract.module.add_function(
+            "sol_keccak256",
+            void_ty.fn_type(&[sol_bytes.into(), u32_ty.into(), u8_ptr.into()], false),
+            None,
+        );
+        function
+            .as_global_value()
+            .set_unnamed_address(UnnamedAddress::Local);
     }
 
     /// Returns the SolAccountInfo of the executing contract
@@ -2588,11 +2611,90 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
     /// Crypto Hash
     fn hash<'b>(
         &self,
-        _contract: &Contract<'b>,
-        _hash: HashTy,
-        _input: PointerValue<'b>,
-        _input_len: IntValue<'b>,
+        contract: &Contract<'b>,
+        hash: HashTy,
+        input: PointerValue<'b>,
+        input_len: IntValue<'b>,
     ) -> IntValue<'b> {
-        unimplemented!()
+        let (fname, hashlen) = match hash {
+            HashTy::Keccak256 => ("sol_keccak256", 32),
+            HashTy::Ripemd160 => ("ripemd160", 20),
+            HashTy::Sha256 => ("sol_sha256", 32),
+            _ => unreachable!(),
+        };
+
+        let res = contract.builder.build_array_alloca(
+            contract.context.i8_type(),
+            contract.context.i32_type().const_int(hashlen, false),
+            "res",
+        );
+
+        if hash == HashTy::Ripemd160 {
+            contract.builder.build_call(
+                contract.module.get_function(fname).unwrap(),
+                &[input.into(), input_len.into(), res.into()],
+                "hash",
+            );
+        } else {
+            let u8_ptr = contract.context.i8_type().ptr_type(AddressSpace::Generic);
+            let u64_ty = contract.context.i64_type();
+
+            let sol_bytes = contract
+                .context
+                .struct_type(&[u8_ptr.into(), u64_ty.into()], false);
+            let array = contract.builder.build_alloca(sol_bytes, "sol_bytes");
+
+            contract.builder.build_store(
+                contract
+                    .builder
+                    .build_struct_gep(array, 0, "input")
+                    .unwrap(),
+                input,
+            );
+
+            contract.builder.build_store(
+                contract
+                    .builder
+                    .build_struct_gep(array, 1, "input_len")
+                    .unwrap(),
+                contract
+                    .builder
+                    .build_int_z_extend(input_len, u64_ty, "input_len"),
+            );
+
+            contract.builder.build_call(
+                contract.module.get_function(fname).unwrap(),
+                &[
+                    array.into(),
+                    contract.context.i32_type().const_int(1, false).into(),
+                    res.into(),
+                ],
+                "hash",
+            );
+        }
+
+        // bytes32 needs to reverse bytes
+        let temp = contract
+            .builder
+            .build_alloca(contract.llvm_type(&ast::Type::Bytes(hashlen as u8)), "hash");
+
+        contract.builder.build_call(
+            contract.module.get_function("__beNtoleN").unwrap(),
+            &[
+                res.into(),
+                contract
+                    .builder
+                    .build_pointer_cast(
+                        temp,
+                        contract.context.i8_type().ptr_type(AddressSpace::Generic),
+                        "",
+                    )
+                    .into(),
+                contract.context.i32_type().const_int(hashlen, false).into(),
+            ],
+            "",
+        );
+
+        contract.builder.build_load(temp, "hash").into_int_value()
     }
 }

+ 1 - 1
stdlib/Makefile

@@ -4,7 +4,7 @@ CFLAGS=$(TARGET_FLAGS) -emit-llvm -O3 -ffreestanding -fno-builtin -Wall -Wno-unu
 bpf/%.bc wasm/%.bc: %.c
 	$(CC) -c $(CFLAGS) $< -o $@
 
-SOLANA=$(addprefix bpf/,solana.bc bigint.bc format.bc stdlib.bc)
+SOLANA=$(addprefix bpf/,solana.bc bigint.bc format.bc stdlib.bc ripemd160.bc)
 WASM=$(addprefix wasm/,ripemd160.bc stdlib.bc substrate.bc bigint.bc format.bc wasmheap.bc keccak256.bc)
 
 all: $(SOLANA) $(WASM)

BIN
stdlib/bpf/ripemd160.bc


+ 85 - 0
tests/solana.rs

@@ -4,6 +4,7 @@ use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
 use ethabi::Token;
 use libc::c_char;
 use rand::Rng;
+use sha2::{Digest, Sha256};
 use solana_helpers::allocator_bump::Allocator;
 use solana_rbpf::{
     error::EbpfError,
@@ -24,6 +25,7 @@ use std::convert::TryInto;
 use std::io::Write;
 use std::mem::{align_of, size_of};
 use std::rc::Rc;
+use tiny_keccak::{Hasher, Keccak};
 
 mod solana_tests;
 
@@ -233,6 +235,72 @@ impl<'a> SyscallObject<UserError> for Printer<'a> {
     }
 }
 
+struct SolSha256();
+
+impl SyscallObject<UserError> for SolSha256 {
+    fn call(
+        &mut self,
+        src: u64,
+        len: u64,
+        dest: u64,
+        _arg4: u64,
+        _arg5: u64,
+        memory_mapping: &MemoryMapping,
+    ) -> Result<u64, EbpfError<UserError>> {
+        let arrays = translate_slice::<(u64, u64)>(memory_mapping, src, len)?;
+
+        let mut hasher = Sha256::new();
+        for (addr, len) in arrays {
+            let buf = translate_slice::<u8>(memory_mapping, *addr, *len)?;
+            println!("hashing: {}", hex::encode(buf));
+            hasher.update(buf);
+        }
+
+        let hash = hasher.finalize();
+
+        let hash_result = translate_slice_mut::<u8>(memory_mapping, dest, hash.len() as u64)?;
+
+        hash_result.copy_from_slice(&hash);
+
+        println!("sol_sha256: {}", hex::encode(hash));
+
+        Ok(0)
+    }
+}
+
+struct SolKeccak256();
+
+impl SyscallObject<UserError> for SolKeccak256 {
+    fn call(
+        &mut self,
+        src: u64,
+        len: u64,
+        dest: u64,
+        _arg4: u64,
+        _arg5: u64,
+        memory_mapping: &MemoryMapping,
+    ) -> Result<u64, EbpfError<UserError>> {
+        let arrays = translate_slice::<(u64, u64)>(memory_mapping, src, len)?;
+
+        let mut hasher = Keccak::v256();
+        let mut hash = [0u8; 32];
+        for (addr, len) in arrays {
+            let buf = translate_slice::<u8>(memory_mapping, *addr, *len)?;
+            println!("hashing: {}", hex::encode(buf));
+            hasher.update(buf);
+        }
+        hasher.finalize(&mut hash);
+
+        let hash_result = translate_slice_mut::<u8>(memory_mapping, dest, hash.len() as u64)?;
+
+        hash_result.copy_from_slice(&hash);
+
+        println!("sol_keccak256: {}", hex::encode(hash));
+
+        Ok(0)
+    }
+}
+
 // Shamelessly stolen from solana source
 
 /// Dynamic memory allocation syscall called when the BPF program calls
@@ -361,12 +429,14 @@ fn translate_type_inner<'a, T>(
             .map(|value| &mut *(value as *mut T))
     }
 }
+
 fn translate_type<'a, T>(
     memory_mapping: &MemoryMapping,
     vm_addr: u64,
 ) -> Result<&'a T, EbpfError<UserError>> {
     translate_type_inner::<T>(memory_mapping, AccessType::Load, vm_addr).map(|value| &*value)
 }
+
 fn translate_slice<'a, T>(
     memory_mapping: &MemoryMapping,
     vm_addr: u64,
@@ -374,6 +444,15 @@ fn translate_slice<'a, T>(
 ) -> Result<&'a [T], EbpfError<UserError>> {
     translate_slice_inner::<T>(memory_mapping, AccessType::Load, vm_addr, len).map(|value| &*value)
 }
+
+fn translate_slice_mut<'a, T>(
+    memory_mapping: &MemoryMapping,
+    vm_addr: u64,
+    len: u64,
+) -> Result<&'a mut [T], EbpfError<UserError>> {
+    translate_slice_inner::<T>(memory_mapping, AccessType::Store, vm_addr, len)
+}
+
 fn translate_slice_inner<'a, T>(
     memory_mapping: &MemoryMapping,
     access_type: AccessType,
@@ -519,6 +598,12 @@ impl VirtualMachine {
         )
         .unwrap();
 
+        vm.register_syscall_ex("sol_sha256", Syscall::Object(Box::new(SolSha256())))
+            .unwrap();
+
+        vm.register_syscall_ex("sol_keccak256", Syscall::Object(Box::new(SolKeccak256())))
+            .unwrap();
+
         vm.register_syscall_ex(
             "sol_invoke_signed_c",
             Syscall::Object(Box::new(SyscallInvokeSignedC {

+ 151 - 0
tests/solana_tests/hash.rs

@@ -0,0 +1,151 @@
+use crate::{build_solidity, first_error, parse_and_resolve};
+use ethabi::Token;
+use solang::Target;
+
+#[test]
+fn constants_hash_tests() {
+    let mut runtime = build_solidity(
+        r##"
+        contract tester {
+            function test() public {
+                bytes32 hash = keccak256("Hello, World!");
+
+                assert(hash == hex"acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f");
+            }
+        }"##,
+    );
+
+    runtime.constructor(&[]);
+    runtime.function("test", &[]);
+
+    let mut runtime = build_solidity(
+        r##"
+        contract tester {
+            function test() public {
+                bytes32 hash = sha256("Hello, World!");
+
+                assert(hash == hex"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f");
+            }
+        }"##,
+    );
+
+    runtime.constructor(&[]);
+    runtime.function("test", &[]);
+
+    let mut runtime = build_solidity(
+        r##"
+        contract tester {
+            function test() public {
+                bytes20 hash = ripemd160("Hello, World!");
+
+                assert(hash == hex"527a6a4b9a6da75607546842e0e00105350b1aaf");
+            }
+        }"##,
+    );
+
+    runtime.constructor(&[]);
+    runtime.function("test", &[]);
+
+    // blake2 hash functions are substrate isms. Ensure they don't exist
+    let ns = parse_and_resolve(
+        r##"
+        contract tester {
+            function test() public {
+                bytes32 hash = blake2_256("Hello, World!");
+
+                assert(hash == hex"527a6a4b9a6da75607546842e0e00105350b1aaf");
+            }
+        }"##,
+        Target::Solana,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "unknown function or type ‘blake2_256’"
+    );
+
+    let ns = parse_and_resolve(
+        r##"
+        contract tester {
+            function test() public {
+                bytes32 hash = blake2_128("Hello, World!");
+
+                assert(hash == hex"527a6a4b9a6da75607546842e0e00105350b1aaf");
+            }
+        }"##,
+        Target::Solana,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "unknown function or type ‘blake2_128’"
+    );
+}
+
+#[test]
+fn hash_tests() {
+    let mut runtime = build_solidity(
+        r##"
+        contract tester {
+            function test(bytes bs) public returns (bytes20) {
+                bytes20 hash = ripemd160(bs);
+
+                return hash;
+            }
+        }"##,
+    );
+
+    runtime.constructor(&[]);
+    let hash = runtime.function("test", &[Token::Bytes(b"Hello, World!".to_vec())]);
+
+    assert_eq!(
+        hash,
+        vec![Token::FixedBytes(
+            hex::decode("527a6a4b9a6da75607546842e0e00105350b1aaf").unwrap()
+        )]
+    );
+
+    let mut runtime = build_solidity(
+        r##"
+        contract tester {
+            function test(bytes bs) public returns (bytes32) {
+                bytes32 hash = sha256(bs);
+
+                return hash;
+            }
+        }"##,
+    );
+
+    runtime.constructor(&[]);
+    let hash = runtime.function("test", &[Token::Bytes(b"Hello, World!".to_vec())]);
+
+    assert_eq!(
+        hash,
+        vec![Token::FixedBytes(
+            hex::decode("dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f")
+                .unwrap()
+        )]
+    );
+
+    let mut runtime = build_solidity(
+        r##"
+        contract tester {
+            function test(bytes bs) public returns (bytes32) {
+                bytes32 hash = keccak256(bs);
+
+                return hash;
+            }
+        }"##,
+    );
+
+    runtime.constructor(&[]);
+    let hash = runtime.function("test", &[Token::Bytes(b"Hello, World!".to_vec())]);
+
+    assert_eq!(
+        hash,
+        vec![Token::FixedBytes(
+            hex::decode("acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f")
+                .unwrap()
+        )]
+    );
+}

+ 1 - 0
tests/solana_tests/mod.rs

@@ -3,6 +3,7 @@ mod accessor;
 mod arrays;
 mod call;
 mod destructure;
+mod hash;
 mod mappings;
 mod primitives;
 mod simple;