Skip to content

Commit

Permalink
Merge pull request #39 from kostko/kostko/feature/more-ops
Browse files Browse the repository at this point in the history
Add SHA-512/256 hash op and FIXED32_LITTLE length op
  • Loading branch information
ethanfrey authored Apr 19, 2021
2 parents 77277ad + 346d8d9 commit 267cfba
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 129 deletions.
11 changes: 10 additions & 1 deletion go/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ics23
import (
"bytes"
"crypto"
"encoding/binary"

// adds sha256 capability to crypto.SHA256
_ "crypto/sha256"
Expand Down Expand Up @@ -131,6 +132,10 @@ func doHash(hashOp HashOp, preimage []byte) ([]byte, error) {
hash := crypto.RIPEMD160.New()
hash.Write(tmp)
return hash.Sum(nil), nil
case HashOp_SHA512_256:
hash := crypto.SHA512_256.New()
hash.Write(preimage)
return hash.Sum(nil), nil
}
return nil, errors.Errorf("Unsupported hashop: %d", hashOp)
}
Expand All @@ -154,11 +159,15 @@ func doLengthOp(lengthOp LengthOp, data []byte) ([]byte, error) {
return nil, errors.Errorf("Data was %d bytes, not 64", len(data))
}
return data, nil
case LengthOp_FIXED32_LITTLE:
res := make([]byte, 4, 4+len(data))
binary.LittleEndian.PutUint32(res[:4], uint32(len(data)))
res = append(res, data...)
return res, nil
// TODO
// case LengthOp_VAR_RLP:
// case LengthOp_FIXED32_BIG:
// case LengthOp_FIXED64_BIG:
// case LengthOp_FIXED32_LITTLE:
// case LengthOp_FIXED64_LITTLE:
}
return nil, errors.Errorf("Unsupported lengthop: %d", lengthOp)
Expand Down
19 changes: 19 additions & 0 deletions go/ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ func TestLeafOp(t *testing.T) {
// echo -n 04666f6f6420a48c2d4f67b9f80374938535285ed285819d8a5a8fc1fccd1e3244e437cf290d | xxd -r -p | sha256sum
expected: fromHex("87e0483e8fb624aef2e2f7b13f4166cda485baa8e39f437c83d74c94bedb148f"),
},
"hash with length prefix (fixed 32-bit little-endian encoding)": {
op: &LeafOp{
Hash: HashOp_SHA256,
Length: LengthOp_FIXED32_LITTLE,
// no prehash
},
// echo -n food | xxs -ps
// and manually compute length bytes
key: []byte("food"), // 04000000666f6f64
value: []byte("some longer text"), // 10000000736f6d65206c6f6e6765722074657874
// echo -n 04000000666f6f6410000000736f6d65206c6f6e6765722074657874 | xxd -r -p | sha256sum
expected: fromHex("c853652437be02501c674744bf2a2b45d92a0a9f29c4b1044010fb3e2d43a949"),
},
}

for name, tc := range cases {
Expand Down Expand Up @@ -199,6 +212,12 @@ func TestDoHash(t *testing.T) {
// echo -n c1f026582fe6e8cb620d0c85a72fe421ddded756662a8ec00ed4c297ad10676b | xxd -r -p | openssl dgst -rmd160 -hex
expectedHash: "0bcb587dfb4fc10b36d57f2bba1878f139b75d24",
},
"sha512_256": {
hashOp: HashOp_SHA512_256,
preimage: "food",
// echo -n food | openssl dgst -sha512-256 -hex | cut -d' ' -f2
expectedHash: "5b3a452a6acbf1fc1e553a40c501585d5bd3cca176d562e0a0e19a3c43804e88",
},
}

for name, tc := range cases {
Expand Down
212 changes: 88 additions & 124 deletions go/proofs.pb.go

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"dependencies": {
"protobufjs": "^6.8.8",
"ripemd160": "^2.0.2",
"sha.js": "^2.4.11"
"sha.js": "^2.4.11",
"js-sha512": "^0.8.0"
}
}
3 changes: 2 additions & 1 deletion js/src/generated/codecimpl.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export namespace ics23 {
SHA512 = 2,
KECCAK = 3,
RIPEMD160 = 4,
BITCOIN = 5
BITCOIN = 5,
SHA512_256 = 6
}

/**
Expand Down
27 changes: 27 additions & 0 deletions js/src/generated/codecimpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ $root.ics23 = (function() {
* @property {number} KECCAK=3 KECCAK value
* @property {number} RIPEMD160=4 RIPEMD160 value
* @property {number} BITCOIN=5 BITCOIN value
* @property {number} SHA512_256=6 SHA512_256 value
*/
ics23.HashOp = (function() {
var valuesById = {}, values = Object.create(valuesById);
Expand All @@ -37,6 +38,7 @@ $root.ics23 = (function() {
values[valuesById[3] = "KECCAK"] = 3;
values[valuesById[4] = "RIPEMD160"] = 4;
values[valuesById[5] = "BITCOIN"] = 5;
values[valuesById[6] = "SHA512_256"] = 6;
return values;
})();

Expand Down Expand Up @@ -1161,6 +1163,7 @@ $root.ics23 = (function() {
case 3:
case 4:
case 5:
case 6:
break;
}
if (message.prehashKey != null && message.hasOwnProperty("prehashKey"))
Expand All @@ -1173,6 +1176,7 @@ $root.ics23 = (function() {
case 3:
case 4:
case 5:
case 6:
break;
}
if (message.prehashValue != null && message.hasOwnProperty("prehashValue"))
Expand All @@ -1185,6 +1189,7 @@ $root.ics23 = (function() {
case 3:
case 4:
case 5:
case 6:
break;
}
if (message.length != null && message.hasOwnProperty("length"))
Expand Down Expand Up @@ -1245,6 +1250,10 @@ $root.ics23 = (function() {
case 5:
message.hash = 5;
break;
case "SHA512_256":
case 6:
message.hash = 6;
break;
}
switch (object.prehashKey) {
case "NO_HASH":
Expand All @@ -1271,6 +1280,10 @@ $root.ics23 = (function() {
case 5:
message.prehashKey = 5;
break;
case "SHA512_256":
case 6:
message.prehashKey = 6;
break;
}
switch (object.prehashValue) {
case "NO_HASH":
Expand All @@ -1297,6 +1310,10 @@ $root.ics23 = (function() {
case 5:
message.prehashValue = 5;
break;
case "SHA512_256":
case 6:
message.prehashValue = 6;
break;
}
switch (object.length) {
case "NO_PREFIX":
Expand Down Expand Up @@ -1579,6 +1596,7 @@ $root.ics23 = (function() {
case 3:
case 4:
case 5:
case 6:
break;
}
if (message.prefix != null && message.hasOwnProperty("prefix"))
Expand Down Expand Up @@ -1627,6 +1645,10 @@ $root.ics23 = (function() {
case 5:
message.hash = 5;
break;
case "SHA512_256":
case 6:
message.hash = 6;
break;
}
if (object.prefix != null)
if (typeof object.prefix === "string")
Expand Down Expand Up @@ -2208,6 +2230,7 @@ $root.ics23 = (function() {
case 3:
case 4:
case 5:
case 6:
break;
}
return null;
Expand Down Expand Up @@ -2268,6 +2291,10 @@ $root.ics23 = (function() {
case 5:
message.hash = 5;
break;
case "SHA512_256":
case 6:
message.hash = 6;
break;
}
return message;
};
Expand Down
25 changes: 25 additions & 0 deletions js/src/ops.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ describe("applyLeaf", () => {
expect(applyLeaf(op, key, value)).toEqual(expected);
});

it("hashes food with sha-512/256", () => {
const op: ics23.ILeafOp = { hash: ics23.HashOp.SHA512_256 };
const key = toAscii("fo");
const value = toAscii("od");
const expected = fromHex(
"5b3a452a6acbf1fc1e553a40c501585d5bd3cca176d562e0a0e19a3c43804e88"
);
expect(applyLeaf(op, key, value)).toEqual(expected);
});

it("hashes foobar (different breakpoint)", () => {
const op: ics23.ILeafOp = { hash: ics23.HashOp.SHA256 };
const key = toAscii("f");
Expand All @@ -76,6 +86,21 @@ describe("applyLeaf", () => {
expect(applyLeaf(op, key, value)).toEqual(expected);
});

it("hashes with length prefix (fixed 32-bit little-endian encoding)", () => {
const op: ics23.ILeafOp = {
hash: ics23.HashOp.SHA256,
length: ics23.LengthOp.FIXED32_LITTLE
};
// echo -n food | xxd -ps
const key = toAscii("food"); // 04000000666f6f64
const value = toAscii("some longer text"); // 10000000736f6d65206c6f6e6765722074657874
// echo -n 04000000666f6f6410000000736f6d65206c6f6e6765722074657874 | xxd -r -p | sha256sum
const expected = fromHex(
"c853652437be02501c674744bf2a2b45d92a0a9f29c4b1044010fb3e2d43a949"
);
expect(applyLeaf(op, key, value)).toEqual(expected);
});

it("hashes with prehash and length prefix", () => {
const op: ics23.ILeafOp = {
hash: ics23.HashOp.SHA256,
Expand Down
18 changes: 17 additions & 1 deletion js/src/ops.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sha512_256 } from "js-sha512";
import ripemd160 from "ripemd160";
import shajs from "sha.js";

Expand Down Expand Up @@ -109,6 +110,8 @@ export function doHash(hashOp: ics23.HashOp, preimage: Uint8Array): Uint8Array {
return rp160(preimage);
case ics23.HashOp.BITCOIN:
return rp160(s256(preimage));
case ics23.HashOp.SHA512_256:
return new Uint8Array(sha512_256.arrayBuffer(preimage));
}
throw new Error(`Unsupported hashop: ${hashOp}`);
}
Expand All @@ -131,11 +134,12 @@ function doLengthOp(lengthOp: ics23.LengthOp, data: Uint8Array): Uint8Array {
throw new Error(`Length is ${data.length}, not 64 bytes`);
}
return data;
case ics23.LengthOp.FIXED32_LITTLE:
return new Uint8Array([...encodeFixed32LE(data.length), ...data]);
// TODO
// case LengthOp_VAR_RLP:
// case LengthOp_FIXED32_BIG:
// case LengthOp_FIXED64_BIG:
// case LengthOp_FIXED32_LITTLE:
// case LengthOp_FIXED64_LITTLE:
}
throw new Error(`Unsupported lengthop: ${lengthOp}`);
Expand All @@ -152,3 +156,15 @@ function encodeVarintProto(n: number): Uint8Array {
enc = [...enc, l];
return new Uint8Array(enc);
}

function encodeFixed32LE(n: number): Uint8Array {
const enc = new Uint8Array(4);
let l = n;
for (let i = enc.length; i > 0; i--) {
/* tslint:disable */
enc[Math.abs(i - enc.length)] = l % 256;
/* tslint:enable */
l = Math.floor(l / 256);
}
return enc;
}
5 changes: 5 additions & 0 deletions js/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ jasmine@^3.5.0:
glob "^7.1.4"
jasmine-core "~3.5.0"

js-sha512@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/js-sha512/-/js-sha512-0.8.0.tgz#dd22db8d02756faccf19f218e3ed61ec8249f7d4"
integrity sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==

js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
Expand Down
1 change: 1 addition & 0 deletions proofs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum HashOp {
KECCAK = 3;
RIPEMD160 = 4;
BITCOIN = 5; // ripemd160(sha256(x))
SHA512_256 = 6;
}

/**
Expand Down
1 change: 1 addition & 0 deletions rust/src/ics23.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ pub enum HashOp {
Ripemd160 = 4,
/// ripemd160(sha256(x))
Bitcoin = 5,
Sha512_256 = 6,
}
///*
///LengthOp defines how to process the key and value of the LeafOp
Expand Down
23 changes: 22 additions & 1 deletion rust/src/ops.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{bail, ensure};
use ripemd160::Ripemd160;
use sha2::{Digest, Sha256, Sha512};
use sha2::{Digest, Sha256, Sha512, Sha512Trunc256};
use sha3::Sha3_512;
use std::convert::TryInto;

Expand Down Expand Up @@ -41,6 +41,7 @@ fn do_hash(hash: HashOp, data: &[u8]) -> Result<Hash> {
HashOp::Bitcoin => Ok(Hash::from(
Ripemd160::digest(Sha256::digest(data).as_slice()).as_slice(),
)),
HashOp::Sha512_256 => Ok(Hash::from(Sha512Trunc256::digest(data).as_slice())),
}
}

Expand All @@ -54,6 +55,11 @@ fn do_length(length: LengthOp, data: &[u8]) -> Result<Hash> {
len.extend(data);
return Ok(len);
}
LengthOp::Fixed32Little => {
let mut len = (data.len() as u32).to_le_bytes().to_vec();
len.extend(data);
return Ok(len);
}
_ => bail!("Unsupported LengthOp {:?}", length),
}
// if we don't error above or return custom string, just return item untouched (common case)
Expand Down Expand Up @@ -99,6 +105,14 @@ mod tests {
"bitcoin hash fails"
);

let hash = do_hash(HashOp::Sha512_256, b"food")?;
ensure!(
hash == hex::decode(
"5b3a452a6acbf1fc1e553a40c501585d5bd3cca176d562e0a0e19a3c43804e88"
)?,
"sha512/256 hash fails"
);

Ok(())
}

Expand All @@ -116,6 +130,13 @@ mod tests {
"proto prefix returned {}",
hex::encode(&prefixed),
);

let prefixed = do_length(LengthOp::Fixed32Little, b"food")?;
ensure!(
prefixed == hex::decode("04000000666f6f64")?,
"proto prefix returned {}",
hex::encode(&prefixed),
);
Ok(())
}

Expand Down

0 comments on commit 267cfba

Please sign in to comment.