Skip to content
This repository has been archived by the owner on Aug 2, 2021. It is now read-only.

contracts, swarm: implement EIP-1577 #1232

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f2aab02
contracts/ens: update public resolver solidity code
acud Feb 18, 2019
e2e7ce0
contracts/ens: update public resolver, update go bindings
acud Feb 19, 2019
b974ee1
update build
acud Feb 19, 2019
49d8157
fix ens.sol
acud Feb 20, 2019
0a4176d
contracts/ens: change contract interface
acud Feb 20, 2019
600ade0
contracts/ens: implement public resolver changes
acud Feb 20, 2019
3e47ff2
contracts/ens: added ENSRegistry contract
acud Feb 20, 2019
181048d
contracts/ens: reinstate old contract code
acud Feb 20, 2019
cb6fd51
contracts/ens: update README.md
acud Feb 20, 2019
b9a2722
contracts/ens: added test coverage for fallback contract
acud Feb 20, 2019
8edf1bf
contracts/ens: added support for fallback contract
acud Feb 20, 2019
b6bdfba
contracts/ens: removed unused contract code
acud Feb 20, 2019
c77795f
contracts/ens: add todo and decode multicodec stub
acud Feb 20, 2019
68e4f0f
add encode
acud Feb 20, 2019
29d9b6b
vendor: add ipfs cid libraries
acud Mar 6, 2019
5852134
contracts/ens: cid sanity tests
acud Mar 6, 2019
1032f14
contracts/ens: more cid sanity checks
acud Mar 6, 2019
36822f8
contracts/ens: wip integration
acud Mar 7, 2019
e8f983e
wip
acud Mar 7, 2019
2168ea0
Revert "vendor: add ipfs cid libraries"
acud Mar 7, 2019
3a14b32
contracts/ens: removed multiformats dependencies
acud Mar 8, 2019
46c2c1c
contracts/ens: added decode tests
acud Mar 8, 2019
14edd10
contracts/ens: added eip spec test, minor changes to exiting tests
acud Mar 8, 2019
32de609
contracts/ens: moved cid decoding to own file
acud Mar 8, 2019
fbca442
contracts/ens: added unit test to encode hash to content hash
acud Mar 8, 2019
5dad797
contracts/ens: removed unused code
acud Mar 8, 2019
4a60133
contracts/ens: fix ens tests to use cid decode and encode
acud Mar 8, 2019
408bfd8
contracts/ens: adjust swarm multicodecs after pr merge
acud Mar 15, 2019
1f9594e
contracts/ens: fix linter error
acud Mar 15, 2019
f8e76bc
constracts/ens: address PR comments
acud Mar 16, 2019
2d1fffe
cmd, contracts: make peoples lives easier
acud Mar 18, 2019
00fb2f3
contracts/ens: fix linter error
acud Mar 18, 2019
31df1cf
contracts/ens: address PR comments
acud Mar 19, 2019
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
59 changes: 58 additions & 1 deletion cmd/swarm/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ package main

import (
"context"
"encoding/hex"
"fmt"
"os"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/swarm/storage"
"gopkg.in/urfave/cli.v1"
)
Expand All @@ -34,7 +37,33 @@ var hashCommand = cli.Command{
Usage: "print the swarm hash of a file or directory",
ArgsUsage: "<file>",
Description: "Prints the swarm hash of file or directory",
}
Subcommands: []cli.Command{
{
CustomHelpTemplate: helpTemplate,
Name: "ens",
Usage: "converts a swarm hash to an ens EIP1577 compatible CIDv1 hash",
ArgsUsage: "<ref>",
Description: "",
Subcommands: []cli.Command{
{
Action: encodeEipHash,
CustomHelpTemplate: helpTemplate,
Name: "contenthash",
Usage: "converts a swarm hash to an ens EIP1577 compatible CIDv1 hash",
ArgsUsage: "<ref>",
Description: "",
},
{
Action: ensNodeHash,
CustomHelpTemplate: helpTemplate,
Name: "node",
Usage: "converts an ens name to an ENS node hash",
ArgsUsage: "<ref>",
Description: "",
},
},
},
}}

func hash(ctx *cli.Context) {
args := ctx.Args()
Expand All @@ -56,3 +85,31 @@ func hash(ctx *cli.Context) {
fmt.Printf("%v\n", addr)
}
}
func ensNodeHash(ctx *cli.Context) {
args := ctx.Args()
if len(args) < 1 {
utils.Fatalf("Usage: swarm hash ens node <ens name>")
}
ensName := args[0]

hash := ens.EnsNode(ensName)

stringHex := hex.EncodeToString(hash[:])
fmt.Println(stringHex)
}
func encodeEipHash(ctx *cli.Context) {
args := ctx.Args()
if len(args) < 1 {
utils.Fatalf("Usage: swarm hash ens <swarm hash>")
}
swarmHash := args[0]

hash := common.HexToHash(swarmHash)
ensHash, err := ens.EncodeSwarmHash(hash)
if err != nil {
utils.Fatalf("error converting swarm hash", err)
}

stringHex := hex.EncodeToString(ensHash)
fmt.Println(stringHex)
}
10 changes: 10 additions & 0 deletions contracts/ens/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ The go bindings for ENS contracts are generated using `abigen` via the go genera
```shell
go generate ./contracts/ens
```

## Fallback contract support

In order to better support content resolution on different service providers (such as Swarm and IPFS), [EIP-1577](https://eips.ethereum.org/EIPS/eip-1577)
was introduced and with it changes that allow applications to know _where_ content hashes are stored (i.e. if the
requested hash resides on Swarm or IPFS).

The code under `contracts/ens/contract` reflects the new Public Resolver changes and the code under `fallback_contract` allows
us to support the old contract resolution in cases where the ENS name owner did not update her Resolver contract, until the migration
period ends (date arbitrarily set to June 1st, 2019).
121 changes: 121 additions & 0 deletions contracts/ens/cid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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 (
cidv1 = 0x1

nsIpfs = 0xe3
nsSwarm = 0xe4

swarmTypecode = 0xfa //swarm manifest, see https://github.com/multiformats/multicodec/blob/master/table.csv
swarmHashtype = 0xd6 // BMT, see https://github.com/multiformats/multicodec/blob/master/table.csv

hashLength = 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, errors.New("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*/, decodedHashLength, hashBytes, err := decodeEIP1577ContentHash(buf)

if err != nil {
return common.Hash{}, err
}

if storageNs != nsSwarm {
return common.Hash{}, errors.New("unknown storage system")
}

//todo: for the time being we implement loose enforcement for the EIP rules until ENS manager is updated
/*if contentType != swarmTypecode {
return common.Hash{}, errors.New("unknown content type")
}

if hashType != swarmHashtype {
return common.Hash{}, errors.New("unknown multihash type")
}*/

if decodedHashLength != hashLength {
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
}

func EncodeSwarmHash(hash common.Hash) ([]byte, error) {
var cidBytes []byte
var headerBytes = []byte{
nsSwarm, //swarm namespace
cidv1, // CIDv1
swarmTypecode, // swarm hash
swarmHashtype, // swarm bmt hash
hashLength, //hash length. 32 bytes
}

varintbuf := make([]byte, binary.MaxVarintLen64)
for _, v := range headerBytes {
n := binary.PutUvarint(varintbuf, uint64(v))
cidBytes = append(cidBytes, varintbuf[:n]...)
}

cidBytes = append(cidBytes, hash[:]...)
return cidBytes, nil
}
158 changes: 158 additions & 0 deletions contracts/ens/cid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick but tests like this are much more readable when they are in table-driven format, and you have to input and wantXXX (expectations) next to each other. Currently you have to look for consts outside the test and you have to read carefully the test to see what's compared where - otherwise you know that all wantXXX expectations are checked.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool. i copied the consts into the individual tests for readability sake. this specific test cannot go into the table format of the test below since it unit tests a different function. is this what you've meant?

const (
eipSpecHash = "e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
eipHash = "29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
dagPb = 0x70
sha2256 = 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, decodedHashBytes, err := decodeEIP1577ContentHash(b)

if err != nil {
t.Fatal(err)
}
if storageNs != nsIpfs {
t.Fatal("wrong ns")
}
if contentType != dagPb {
t.Fatal("should be ipfs typecode")
}
if hashType != sha2256 {
t.Fatal("should be sha2-256")
}
if hashLength != 32 {
t.Fatal("should be 32")
}
if !bytes.Equal(hashBytes, decodedHashBytes) {
t.Fatal("should be equal")
}

}
func TestManualCidDecode(t *testing.T) {
// call cid encode method with hash. expect byte slice returned, compare according to spec

for _, v := range []struct {
name string
headerBytes []byte
wantErr bool
}{
{
name: "values correct, should not fail",
headerBytes: []byte{0xe4, 0x01, 0xfa, 0xd6, 0x20},
wantErr: false,
},
{
name: "cid version wrong, should fail",
headerBytes: []byte{0xe4, 0x00, 0xfa, 0xd6, 0x20},
wantErr: true,
},
{
name: "hash length wrong, should fail",
headerBytes: []byte{0xe4, 0x01, 0xfa, 0xd6, 0x1f},
wantErr: true,
},
{
name: "values correct for ipfs, should fail",
headerBytes: []byte{0xe3, 0x01, 0x70, 0x12, 0x20},
wantErr: true,
},
{
name: "loose values for swarm, todo remove, should not fail",
headerBytes: []byte{0xe4, 0x01, 0x70, 0x12, 0x20},
wantErr: false,
},
{
name: "loose values for swarm, todo remove, should not fail",
headerBytes: []byte{0xe4, 0x01, 0x99, 0x99, 0x20},
wantErr: false,
},
} {
t.Run(v.name, func(t *testing.T) {
const eipHash = "29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"

var bb []byte
buf := make([]byte, binary.MaxVarintLen64)
for _, vv := range v.headerBytes {
n := binary.PutUvarint(buf, uint64(vv))
bb = append(bb, buf[:n]...)
}

h := common.HexToHash(eipHash)
bb = append(bb, h[:]...)
str := hex.EncodeToString(bb)
fmt.Println(str)
decodedHash, e := extractContentHash(bb)
switch v.wantErr {
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")
}
}
})
}
}

func TestManuelCidEncode(t *testing.T) {
// call cid encode method with hash. expect byte slice returned, compare according to spec
const eipHash = "29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
cidBytes, err := EncodeSwarmHash(common.HexToHash(eipHash))
if err != nil {
t.Fatal(err)
}

// logic in extractContentHash is unit tested thoroughly
// hence we just check that the returned hash is equal
h, err := extractContentHash(cidBytes)
if err != nil {
t.Fatal(err)
}

if bytes.Equal(h[:], cidBytes) {
t.Fatal("should be equal")
}
}
23 changes: 0 additions & 23 deletions contracts/ens/contract/AbstractENS.sol

This file was deleted.

Loading