package registrar import ( "encoding/binary" "fmt" "math/big" "regexp" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" ) /* Registrar implements the Ethereum name registrar services mapping - arbitrary strings to ethereum addresses - hashes to hashes - hashes to arbitrary strings (likely will provide lookup service for all three) The Registrar is used by * the roundtripper transport implementation of url schemes to resolve domain names and services that register these names * contract info retrieval (NatSpec). The Registrar uses 3 contracts on the blockchain: * GlobalRegistrar: Name (string) -> Address (Owner) * HashReg : Key Hash (hash of domain name or contract code) -> Content Hash * UrlHint : Content Hash -> Url Hint These contracts are (currently) not included in the genesis block. Each Set needs to be called once on each blockchain/network once. Contract addresses need to be set (HashReg and UrlHint retrieved from the global registrar the first time any Registrar method is called in a client session So the caller needs to make sure the relevant environment initialised the desired contracts */ var ( UrlHintAddr = "0x0" HashRegAddr = "0x0" GlobalRegistrarAddr = "0x0" // GlobalRegistrarAddr = "0xc6d9d2cd449a754c494264e1809c50e34d64562b" zero = regexp.MustCompile("^(0x)?0*$") ) const ( trueHex = "0000000000000000000000000000000000000000000000000000000000000001" falseHex = "0000000000000000000000000000000000000000000000000000000000000000" ) func abiSignature(s string) string { return common.ToHex(crypto.Sha3([]byte(s))[:4]) } var ( HashRegName = "HashReg" UrlHintName = "UrlHint" registerContentHashAbi = abiSignature("register(uint256,uint256)") registerUrlAbi = abiSignature("register(uint256,uint8,uint256)") setOwnerAbi = abiSignature("setowner()") reserveAbi = abiSignature("reserve(bytes32)") resolveAbi = abiSignature("addr(bytes32)") registerAbi = abiSignature("setAddress(bytes32,address,bool)") addressAbiPrefix = falseHex[:24] ) // Registrar's backend is defined as an interface (implemented by xeth, but could be remote) type Backend interface { StorageAt(string, string) string Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) } // TODO Registrar should also just implement The Resolver and Registry interfaces // Simplify for now. type VersionedRegistrar interface { Resolver(*big.Int) *Registrar Registry() *Registrar } type Registrar struct { backend Backend } func New(b Backend) (res *Registrar) { res = &Registrar{b} return } func (self *Registrar) SetGlobalRegistrar(namereg string, addr common.Address) (err error) { if namereg != "" { GlobalRegistrarAddr = namereg return } if GlobalRegistrarAddr == "0x0" || GlobalRegistrarAddr == "0x" { if (addr == common.Address{}) { err = fmt.Errorf("GlobalRegistrar address not found and sender for creation not given") return } else { GlobalRegistrarAddr, err = self.backend.Transact(addr.Hex(), "", "", "", "800000", "", GlobalRegistrarCode) if err != nil { err = fmt.Errorf("GlobalRegistrar address not found and sender for creation failed: %v", err) return } } } return } func (self *Registrar) SetHashReg(hashreg string, addr common.Address) (err error) { if hashreg != "" { HashRegAddr = hashreg } else { if !zero.MatchString(HashRegAddr) { return } nameHex, extra := encodeName(HashRegName, 2) hashRegAbi := resolveAbi + nameHex + extra glog.V(logger.Detail).Infof("\ncall HashRegAddr %v with %v\n", GlobalRegistrarAddr, hashRegAbi) var res string res, _, err = self.backend.Call("", GlobalRegistrarAddr, "", "", "", hashRegAbi) if len(res) >= 40 { HashRegAddr = "0x" + res[len(res)-40:len(res)] } if err != nil || zero.MatchString(HashRegAddr) { if (addr == common.Address{}) { err = fmt.Errorf("HashReg address not found and sender for creation not given") return } HashRegAddr, err = self.backend.Transact(addr.Hex(), "", "", "", "", "", HashRegCode) if err != nil { err = fmt.Errorf("HashReg address not found and sender for creation failed: %v", err) } glog.V(logger.Detail).Infof("created HashRegAddr @ %v\n", HashRegAddr) } else { glog.V(logger.Detail).Infof("HashRegAddr found at @ %v\n", HashRegAddr) return } } // register as HashReg self.ReserveName(addr, HashRegName) self.SetAddressToName(addr, HashRegName, common.HexToAddress(HashRegAddr)) return } func (self *Registrar) SetUrlHint(urlhint string, addr common.Address) (err error) { if urlhint != "" { UrlHintAddr = urlhint } else { if !zero.MatchString(UrlHintAddr) { return } nameHex, extra := encodeName(UrlHintName, 2) urlHintAbi := resolveAbi + nameHex + extra glog.V(logger.Detail).Infof("UrlHint address query data: %s to %s", urlHintAbi, GlobalRegistrarAddr) var res string res, _, err = self.backend.Call("", GlobalRegistrarAddr, "", "", "", urlHintAbi) if len(res) >= 40 { UrlHintAddr = "0x" + res[len(res)-40:len(res)] } if err != nil || zero.MatchString(UrlHintAddr) { if (addr == common.Address{}) { err = fmt.Errorf("UrlHint address not found and sender for creation not given") return } UrlHintAddr, err = self.backend.Transact(addr.Hex(), "", "", "", "210000", "", UrlHintCode) if err != nil { err = fmt.Errorf("UrlHint address not found and sender for creation failed: %v", err) } glog.V(logger.Detail).Infof("created UrlHint @ %v\n", HashRegAddr) } else { glog.V(logger.Detail).Infof("UrlHint found @ %v\n", HashRegAddr) return } } // register as UrlHint self.ReserveName(addr, UrlHintName) self.SetAddressToName(addr, UrlHintName, common.HexToAddress(UrlHintAddr)) return } // ReserveName(from, name) reserves name for the sender address in the globalRegistrar // the tx needs to be mined to take effect func (self *Registrar) ReserveName(address common.Address, name string) (txh string, err error) { nameHex, extra := encodeName(name, 2) abi := reserveAbi + nameHex + extra glog.V(logger.Detail).Infof("Reserve data: %s", abi) return self.backend.Transact( address.Hex(), GlobalRegistrarAddr, "", "", "", "", abi, ) } // SetAddressToName(from, name, addr) will set the Address to address for name // in the globalRegistrar using from as the sender of the transaction // the tx needs to be mined to take effect func (self *Registrar) SetAddressToName(from common.Address, name string, address common.Address) (txh string, err error) { nameHex, extra := encodeName(name, 6) addrHex := encodeAddress(address) abi := registerAbi + nameHex + addrHex + trueHex + extra glog.V(logger.Detail).Infof("SetAddressToName data: %s to %s ", abi, GlobalRegistrarAddr) return self.backend.Transact( from.Hex(), GlobalRegistrarAddr, "", "", "", "", abi, ) } // NameToAddr(from, name) queries the registrar for the address on name func (self *Registrar) NameToAddr(from common.Address, name string) (address common.Address, err error) { nameHex, extra := encodeName(name, 2) abi := resolveAbi + nameHex + extra glog.V(logger.Detail).Infof("NameToAddr data: %s", abi) res, _, err := self.backend.Call( from.Hex(), GlobalRegistrarAddr, "", "", "", abi, ) if err != nil { return } address = common.HexToAddress(res) return } // called as first step in the registration process on HashReg func (self *Registrar) SetOwner(address common.Address) (txh string, err error) { return self.backend.Transact( address.Hex(), HashRegAddr, "", "", "", "", setOwnerAbi, ) } // registers some content hash to a key/code hash // e.g., the contract Info combined Json Doc's ContentHash // to CodeHash of a contract or hash of a domain func (self *Registrar) SetHashToHash(address common.Address, codehash, dochash common.Hash) (txh string, err error) { _, err = self.SetOwner(address) if err != nil { return } codehex := common.Bytes2Hex(codehash[:]) dochex := common.Bytes2Hex(dochash[:]) data := registerContentHashAbi + codehex + dochex glog.V(logger.Detail).Infof("SetHashToHash data: %s sent to %v\n", data, HashRegAddr) return self.backend.Transact( address.Hex(), HashRegAddr, "", "", "", "", data, ) } // SetUrlToHash(from, hash, url) registers a url to a content hash so that the content can be fetched // address is used as sender for the transaction and will be the owner of a new // registry entry on first time use // FIXME: silently doing nothing if sender is not the owner // note that with content addressed storage, this step is no longer necessary func (self *Registrar) SetUrlToHash(address common.Address, hash common.Hash, url string) (txh string, err error) { hashHex := common.Bytes2Hex(hash[:]) var urlHex string urlb := []byte(url) var cnt byte n := len(urlb) for n > 0 { if n > 32 { n = 32 } urlHex = common.Bytes2Hex(urlb[:n]) urlb = urlb[n:] n = len(urlb) bcnt := make([]byte, 32) bcnt[31] = cnt data := registerUrlAbi + hashHex + common.Bytes2Hex(bcnt) + common.Bytes2Hex(common.Hex2BytesFixed(urlHex, 32)) txh, err = self.backend.Transact( address.Hex(), UrlHintAddr, "", "", "", "", data, ) if err != nil { return } cnt++ } return } // HashToHash(key) resolves contenthash for key (a hash) using HashReg // resolution is costless non-transactional // implemented as direct retrieval from db func (self *Registrar) HashToHash(khash common.Hash) (chash common.Hash, err error) { // look up in hashReg at := HashRegAddr[2:] key := storageAddress(storageMapping(storageIdx2Addr(1), khash[:])) hash := self.backend.StorageAt(at, key) if hash == "0x0" || len(hash) < 3 || (hash == common.Hash{}.Hex()) { err = fmt.Errorf("content hash not found for '%v'", khash.Hex()) return } copy(chash[:], common.Hex2BytesFixed(hash[2:], 32)) return } // HashToUrl(contenthash) resolves the url for contenthash using UrlHint // resolution is costless non-transactional // implemented as direct retrieval from db // if we use content addressed storage, this step is no longer necessary func (self *Registrar) HashToUrl(chash common.Hash) (uri string, err error) { // look up in URL reg var str string = " " var idx uint32 for len(str) > 0 { mapaddr := storageMapping(storageIdx2Addr(1), chash[:]) key := storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(idx))) hex := self.backend.StorageAt(UrlHintAddr[2:], key) str = string(common.Hex2Bytes(hex[2:])) l := len(str) for (l > 0) && (str[l-1] == 0) { l-- } str = str[:l] uri = uri + str idx++ } l := 0 for (l < len(uri)) && (uri[l] == 0) { l++ } uri = uri[l:] if len(uri) == 0 { err = fmt.Errorf("GetURLhint: URL hint not found for '%v'", chash.Hex()) } return } func storageIdx2Addr(varidx uint32) []byte { data := make([]byte, 32) binary.BigEndian.PutUint32(data[28:32], varidx) return data } func storageMapping(addr, key []byte) []byte { data := make([]byte, 64) copy(data[0:32], key[0:32]) copy(data[32:64], addr[0:32]) sha := crypto.Sha3(data) return sha } func storageFixedArray(addr, idx []byte) []byte { var carry byte for i := 31; i >= 0; i-- { var b byte = addr[i] + idx[i] + carry if b < addr[i] { carry = 1 } else { carry = 0 } addr[i] = b } return addr } func storageAddress(addr []byte) string { return common.ToHex(addr) } func encodeAddress(address common.Address) string { return addressAbiPrefix + address.Hex()[2:] } func encodeName(name string, index uint8) (string, string) { extra := common.Bytes2Hex([]byte(name)) if len(name) > 32 { return fmt.Sprintf("%064x", index), extra } return extra + falseHex[len(extra):], "" }