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

Add SHA-512/256 hash op and FIXED32_LITTLE length op #39

Merged
merged 2 commits into from
Apr 19, 2021
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
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