contracts/ens: moved cid decoding to own file

This commit is contained in:
Elad 2019-03-08 21:00:17 +07:00
parent 14edd10673
commit 32de60911f
4 changed files with 314 additions and 230 deletions

114
contracts/ens/cid.go Normal file
View file

@ -0,0 +1,114 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ens
import (
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
)
const (
ns_ipfs = 0xe3
ns_swarm = 0xe4
swarm_typecode = 0x99 //todo change
swarm_hashtype = 0x1b //todo change
ipfs_hashtype = 0x12
hash_length = 32
)
// deocodeEIP1577ContentHash decodes a chain-stored content hash from an ENS record according to EIP-1577
// a successful decode will result the different parts of the content hash in accordance to the CID spec
// Note: only CIDv1 is supported
func decodeEIP1577ContentHash(buf []byte) (storageNs, contentType, hashType, hashLength uint64, hash []byte, err error) {
if len(buf) < 10 {
return 0, 0, 0, 0, nil, fmt.Errorf("buffer too short")
}
storageNs, n := binary.Uvarint(buf)
buf = buf[n:]
vers, n := binary.Uvarint(buf)
if vers != 1 {
return 0, 0, 0, 0, nil, fmt.Errorf("expected cid v1, got: %d", vers)
}
buf = buf[n:]
contentType, n = binary.Uvarint(buf)
buf = buf[n:]
hashType, n = binary.Uvarint(buf)
buf = buf[n:]
hashLength, n = binary.Uvarint(buf)
hash = buf[n:]
if len(hash) != int(hashLength) {
return 0, 0, 0, 0, nil, errors.New("hash length mismatch")
}
return storageNs, contentType, hashType, hashLength, hash, nil
}
func extractContentHash(buf []byte) (common.Hash, error) {
storageNs, contentType, hashType, hashLength, hashBytes, err := decodeEIP1577ContentHash(buf)
if err != nil {
return common.Hash{}, err
}
if storageNs != ns_swarm {
return common.Hash{}, errors.New("unknown storage system")
}
if contentType != swarm_typecode { //todo pending pr
return common.Hash{}, errors.New("unknown content type")
}
if hashType != swarm_hashtype { //todo: should be bmt
return common.Hash{}, errors.New("unknown multihash type")
}
if hashLength != hash_length {
return common.Hash{}, errors.New("odd hash length, swarm expects 32 bytes")
}
if len(hashBytes) != int(hashLength) {
return common.Hash{}, errors.New("hash length mismatch")
}
return common.BytesToHash(buf), nil
}
// encodeCid encodes a swarm hash into an IPLD CID
/*func encodeCid(h common.Hash) (cid.Cid, error) {
b := []byte{0x1b, 0x20} //0x1b = keccak256 (should be changed to bmt), 0x20 = 32 bytes hash length
b = append(b, h.Bytes()...) // append actual hash bytes
multi, err := mh.Cast(b)
if err != nil {
return cid.Cid{}, err
}
c := cid.NewCidV1(cid.Raw, multi) //todo: cid.Raw should be swarm manifest
return c, nil
}*/

200
contracts/ens/cid_test.go Normal file
View file

@ -0,0 +1,200 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ens
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common"
)
// Tests for the decoding of the example ENS
func TestEIPSpecCidDecode(t *testing.T) {
const (
eipSpecHash = "e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
eipHash = "29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
dag_pb = 0x70
sha2_256 = 0x12
)
b, err := hex.DecodeString(eipSpecHash)
if err != nil {
t.Fatal(err)
}
hashBytes, err := hex.DecodeString(eipHash)
if err != nil {
t.Fatal(err)
}
storageNs, contentType, hashType, hashLength, hashBytes, err := decodeEIP1577ContentHash(b)
if err != nil {
t.Fatal(err)
}
if storageNs != ns_ipfs {
t.Fatal("wrong ns")
}
if contentType != dag_pb {
t.Fatal("should be swarm typecode")
}
if hashType != sha2_256 {
t.Fatal("should be sha2-256")
}
if hashLength != 32 {
t.Fatal("should be 32")
}
if !bytes.Equal(hashBytes, hashBytes) {
t.Fatal("should be equal")
}
}
func TestManualCidDecode(t *testing.T) {
// call cid encode method with hash. expect byte slice returned, compare according to spec
bb := []byte{}
for _, v := range []struct {
name string
headerBytes []byte
fails bool
}{
{
name: "values correct, should not fail",
headerBytes: []byte{0xe4, 0x01, 0x99, 0x1b, 0x20},
fails: false,
},
{
name: "cid version wrong, should fail",
headerBytes: []byte{0xe4, 0x00, 0x99, 0x1b, 0x20},
fails: true,
},
{
name: "hash length wrong, should fail",
headerBytes: []byte{0xe4, 0x01, 0x99, 0x1b, 0x1f},
fails: true,
},
{
name: "values correct for ipfs, should fail",
headerBytes: []byte{0xe3, 0x01, 0x99, 0x1b, 0x20},
fails: true,
},
} {
t.Run(v.name, func(t *testing.T) {
buf := make([]byte, binary.MaxVarintLen64)
for _, vv := range v.headerBytes {
n := binary.PutUvarint(buf, uint64(vv))
bb = append(bb, buf[:n]...)
}
h := common.HexToHash("29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f")
bb = append(bb, h[:]...)
str := hex.EncodeToString(bb)
fmt.Println(str)
decodedHash, e := extractContentHash(bb)
switch v.fails {
case true:
if e == nil {
t.Fatal("the decode should fail")
}
case false:
if e != nil {
t.Fatalf("the deccode shouldnt fail: %v", e)
}
if !bytes.Equal(decodedHash[:], h[:]) {
t.Fatal("hashes not equal")
}
}
})
}
/* from the EIP documentation
storage system: Swarm (0xe4)
CID version: 1 (0x01)
content type: swarm-manifest (0x??)
hash function: keccak-256 (0x1B)
hash length: 32 bytes (0x20)
hash: 29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f
*/
}
func TestManuelCidEncode(t *testing.T) {
// call cid encode method with hash. expect byte slice returned, compare according to spec
/* from the EIP documentation
storage system: Swarm (0xe4)
CID version: 1 (0x01)
content type: swarm-manifest (0x??)
hash function: keccak-256 (0x1B)
hash length: 32 bytes (0x20)
hash: 29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f
*/
}
/*
func TestCIDSanity(t *testing.T) {
hashStr := "d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162"
hash := common.HexToHash(hashStr) //this always yields a 32 byte long hash
cc, err := encodeCid(hash)
if err != nil {
t.Fatal(err)
}
if cc.Prefix().MhLength != 32 {
t.Fatal("w00t")
}
decoded, err := mh.Decode(cc.Hash())
if err != nil {
t.Fatal(err)
}
if decoded.Length != 32 {
t.Fatal("invalid length")
}
if !bytes.Equal(decoded.Digest, hash[:]) {
t.Fatalf("hashes not equal")
}
if decoded.Length != 32 {
t.Fatal("wrong length")
}
fmt.Println(cc.StringOfBase(multibase.Base16))
bbbb, e := cc.StringOfBase(multibase.Base16)
if e != nil {
t.Fatal(e)
}
fmt.Println(bbbb)
//create the CID string artificially
hashStr = "f01551b20" + hashStr
c, err := cid.Decode(hashStr)
if err != nil {
t.Fatalf("Error decoding CID: %v", err)
}
fmt.Sprintf("Got CID: %v", c)
fmt.Println("Got CID:", c.Prefix())
}*/

View file

@ -23,8 +23,6 @@ package ens
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"strings" "strings"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -262,61 +260,3 @@ func (ens *ENS) SetContentHash(name string, hash []byte) (*types.Transaction, er
// END DEPRECATED CODE // END DEPRECATED CODE
return resolver.Contract.SetContenthash(&opts, node, hash) return resolver.Contract.SetContenthash(&opts, node, hash)
} }
func manualDecode(buf []byte) (common.Hash, error) {
if len(buf) < 2 {
return common.Hash{}, errors.New("buffer too short")
}
storageSys, n := binary.Uvarint(buf)
if storageSys != 0xe3 && storageSys != 0xe4 {
return common.Hash{}, errors.New("unknown storage system")
}
buf = buf[n:]
vers, n := binary.Uvarint(buf)
if vers != 1 {
return common.Hash{}, fmt.Errorf("expected 1 as the cid version number, got: %d", vers)
}
buf = buf[n:]
ctype, n := binary.Uvarint(buf)
if ctype < 0 { // ctype != 0x99 {
return common.Hash{}, errors.New("unknown content type")
}
buf = buf[n:]
hashType, n := binary.Uvarint(buf)
if hashType != 0x1b && hashType != 0x12 {
return common.Hash{}, errors.New("unknown multihash type")
}
buf = buf[n:]
hashLen, n := binary.Uvarint(buf)
if hashLen != 32 {
return common.Hash{}, errors.New("odd hash length, swarm expects 32 bytes")
}
buf = buf[n:]
if len(buf) != int(hashLen) {
return common.Hash{}, errors.New("hash length mismatch")
}
return common.BytesToHash(buf), nil
}
// encodeCid encodes a swarm hash into an IPLD CID
/*func encodeCid(h common.Hash) (cid.Cid, error) {
b := []byte{0x1b, 0x20} //0x1b = keccak256 (should be changed to bmt), 0x20 = 32 bytes hash length
b = append(b, h.Bytes()...) // append actual hash bytes
multi, err := mh.Cast(b)
if err != nil {
return cid.Cid{}, err
}
c := cid.NewCidV1(cid.Raw, multi) //todo: cid.Raw should be swarm manifest
return c, nil
}*/

View file

@ -17,10 +17,6 @@
package ens package ens
import ( import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"math/big" "math/big"
"testing" "testing"
@ -124,169 +120,3 @@ func TestENS(t *testing.T) {
} }
t.Fatal("todo: try to set old contract with new multicodec stuff and assert fail, set new contract with multicodec stuff, encode, decode and assert returns correct hash") t.Fatal("todo: try to set old contract with new multicodec stuff and assert fail, set new contract with multicodec stuff, encode, decode and assert returns correct hash")
} }
func TestEIPSpecCidDecode(t *testing.T) {
/*storage system: IPFS (0xe3)
CID version: 1 (0x01)
content type: dag-pb (0x70)
hash function: sha2-256 (0x12)
hash length: 32 bytes (0x20)
hash: 29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f
*/
const eipSpecHash = "e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
const eipHash = "29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
b, err := hex.DecodeString(eipSpecHash)
if err != nil {
t.Fatal(err)
}
hashBytes, err := hex.DecodeString(eipHash)
if err != nil {
t.Fatal(err)
}
h, err := manualDecode(b)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(h[:], hashBytes) {
t.Fatal("should be equal")
}
}
func TestManualCidDecode(t *testing.T) {
// call cid encode method with hash. expect byte slice returned, compare according to spec
bb := []byte{}
for _, v := range []struct {
name string
headerBytes []byte
fails bool
}{
{
name: "values correct, should not fail",
headerBytes: []byte{0xe4, 0x01, 0x99, 0x1b, 0x20},
fails: false,
},
{
name: "cid version wrong, should fail",
headerBytes: []byte{0xe4, 0x01, 0x99, 0x1b, 0x20},
fails: true,
},
{
name: "hash length wrong, should fail",
headerBytes: []byte{0xe4, 0x01, 0x99, 0x1b, 0x1f},
fails: true,
},
{
name: "values correct for ipfs, should fail",
headerBytes: []byte{0xe3, 0x01, 0x99, 0x1b, 0x20},
fails: true,
},
} {
t.Run(v.name, func(t *testing.T) {
buf := make([]byte, binary.MaxVarintLen64)
for _, vv := range v.headerBytes {
n := binary.PutUvarint(buf, uint64(vv))
bb = append(bb, buf[:n]...)
}
h := common.HexToHash("29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f")
bb = append(bb, h[:]...)
str := hex.EncodeToString(bb)
fmt.Println(str)
decodedHash, e := manualDecode(bb)
switch v.fails {
case true:
if e == nil {
t.Fatal("the decode should fail")
}
case false:
if e != nil {
t.Fatal("the deccode shouldnt fail")
}
}
if e != nil {
t.Fatal(e)
}
if !bytes.Equal(decodedHash[:], h[:]) {
t.Fatal("hashes not equal")
}
})
}
/* from the EIP documentation
storage system: Swarm (0xe4)
CID version: 1 (0x01)
content type: swarm-manifest (0x??)
hash function: keccak-256 (0x1B)
hash length: 32 bytes (0x20)
hash: 29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f
*/
}
func TestManuelCidEncode(t *testing.T) {
// call cid encode method with hash. expect byte slice returned, compare according to spec
/* from the EIP documentation
storage system: Swarm (0xe4)
CID version: 1 (0x01)
content type: swarm-manifest (0x??)
hash function: keccak-256 (0x1B)
hash length: 32 bytes (0x20)
hash: 29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f
*/
}
/*
func TestCIDSanity(t *testing.T) {
hashStr := "d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162"
hash := common.HexToHash(hashStr) //this always yields a 32 byte long hash
cc, err := encodeCid(hash)
if err != nil {
t.Fatal(err)
}
if cc.Prefix().MhLength != 32 {
t.Fatal("w00t")
}
decoded, err := mh.Decode(cc.Hash())
if err != nil {
t.Fatal(err)
}
if decoded.Length != 32 {
t.Fatal("invalid length")
}
if !bytes.Equal(decoded.Digest, hash[:]) {
t.Fatalf("hashes not equal")
}
if decoded.Length != 32 {
t.Fatal("wrong length")
}
fmt.Println(cc.StringOfBase(multibase.Base16))
bbbb, e := cc.StringOfBase(multibase.Base16)
if e != nil {
t.Fatal(e)
}
fmt.Println(bbbb)
//create the CID string artificially
hashStr = "f01551b20" + hashStr
c, err := cid.Decode(hashStr)
if err != nil {
t.Fatalf("Error decoding CID: %v", err)
}
fmt.Sprintf("Got CID: %v", c)
fmt.Println("Got CID:", c.Prefix())
}*/