diff --git a/contracts/ens/cid.go b/contracts/ens/cid.go new file mode 100644 index 0000000000..bf19d1080c --- /dev/null +++ b/contracts/ens/cid.go @@ -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 . + +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 +}*/ diff --git a/contracts/ens/cid_test.go b/contracts/ens/cid_test.go new file mode 100644 index 0000000000..8ee4f1b5ca --- /dev/null +++ b/contracts/ens/cid_test.go @@ -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 . + +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()) + +}*/ diff --git a/contracts/ens/ens.go b/contracts/ens/ens.go index 4a910fcff9..bbca8f2028 100644 --- a/contracts/ens/ens.go +++ b/contracts/ens/ens.go @@ -23,8 +23,6 @@ package ens import ( "encoding/binary" - "errors" - "fmt" "strings" "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 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 -}*/ diff --git a/contracts/ens/ens_test.go b/contracts/ens/ens_test.go index e225238321..764a575cb7 100644 --- a/contracts/ens/ens_test.go +++ b/contracts/ens/ens_test.go @@ -17,10 +17,6 @@ package ens import ( - "bytes" - "encoding/binary" - "encoding/hex" - "fmt" "math/big" "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") } - -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()) - -}*/