-
-
Save Stebalien/fd29d46eb24bcb72fe781cf5a09e7aa7 to your computer and use it in GitHub Desktop.
Example f4/0x conversion code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"encoding/base32" | |
"encoding/hex" | |
"fmt" | |
"strings" | |
"golang.org/x/crypto/blake2b" | |
) | |
// Lower-case rfc 4648 alphabet. | |
const base32Alphabet = "abcdefghijklmnopqrstuvwxyz234567" | |
const fevmPrefix string = "f410f" | |
const ethPrefix string = "0x" | |
const checksumLen = 4 | |
const payloadLen = 20 // 0x-style address length | |
// The prefix of id-in-evm addresses. | |
var ( | |
maskedIDPrefix = [20 - 8]byte{0xff} // 0xff followed by 11 zeros | |
fevmPrefixBytes = []byte{4, 10} | |
) | |
var AddressEncoding = base32.NewEncoding(base32Alphabet).WithPadding(base32.NoPadding) | |
// Converts an address starting with f410f to an ethereum-style (0x) address. | |
// | |
// NOTE: If the resulting address falls in the range: | |
// | |
// 0xff00000000000000000000000000000000000000 - 0xff0000000000000000000000ffffffffffffffff | |
// | |
// It is considered invalid. Technically, funds can be sent to such an address, but no actor/account | |
// can be deployed there. | |
// | |
// Addresses in this range are reserved for masked actor IDs in 0x-style addresses, providing a | |
// way for ethereum tooling to send messages to Filecoin actors outside of the FEVM ecosystem. | |
func FEVMToEthStyle(addr string) (string, error) { | |
// We first validate that we have a proper f410f address by checking the prefix. | |
// | |
// For context: | |
// 1. f4 - address class. | |
// 2. 10 - the actor ID of the EAM (Ethereum Address Manager). | |
// 3. f - a final separator. | |
if !strings.HasPrefix(addr, fevmPrefix) { | |
return "", fmt.Errorf("not an fevm (f410f) address") | |
} | |
payloadStr := addr[len(fevmPrefix):] | |
// Decode as lower-case RFC 4648 base32. We expect the decoded payload to be exactly 24 | |
// bytes long. | |
// | |
// BEWARE: It's possible to send funds to f4 addresses with invalid lengths. These funds can | |
// never be spent (the address will never be assigned to an account or an actor), but there | |
// will be "placeholders" on-chain with strange f410f addresses. | |
payload, err := AddressEncoding.DecodeString(payloadStr) | |
if err != nil { | |
return "", fmt.Errorf("failed to decode address payload: %w", err) | |
} | |
if len(payload) != checksumLen+payloadLen { | |
return "", fmt.Errorf("incorrect length") | |
} | |
// Split it into a payload/checksum. The last 4 bytes are always the checksum. | |
checksum := payload[payloadLen:] | |
payload = payload[:payloadLen] | |
// optionally validate the checksum. | |
actualChecksum := Checksum(payload) | |
if !bytes.Equal(actualChecksum, checksum) { | |
return "", fmt.Errorf("failed to validate checksum") | |
} | |
// Reject masked ID addresses as described in the function documentation. | |
if bytes.HasPrefix(payload, maskedIDPrefix[:]) { | |
return "", fmt.Errorf("unassignable address") | |
} | |
return fmt.Sprintf("0x%x", payload), nil | |
} | |
// Converts an ethereum-style (0x) address to an f410f address. This function _currently_ only | |
// handles ethereum-style addresses that map to an f410f address, it rejects ethereum-style | |
// addresses that would map to an f0 address (a so-called "masked ID address"). | |
func EthStyleToFEVM(addr string) (string, error) { | |
// Parse the 0x-style address | |
if !strings.HasPrefix(addr, ethPrefix) { | |
return "", fmt.Errorf("not an eth-style address") | |
} | |
payloadStr := addr[len(ethPrefix):] | |
payload, err := hex.DecodeString(strings.ToLower(payloadStr)) | |
if err != nil { | |
return "", fmt.Errorf("failed to decode address payload: %w", err) | |
} | |
if len(payload) != 20 { | |
return "", fmt.Errorf("incorrect length") | |
} | |
// Reject it if this 0x-style address is a "masked ID address" | |
if bytes.HasPrefix(payload, maskedIDPrefix[:]) { | |
return "", fmt.Errorf("masked-id-address") | |
} | |
// Compute the checksum, and append it to the payload. | |
checksum := Checksum(payload) | |
encodedPayload := AddressEncoding.EncodeToString(append(payload, checksum...)) | |
// Encode the f410f-style address. | |
return fmt.Sprintf("%s%s", fevmPrefix, encodedPayload), nil | |
} | |
// Checksum computes the 4 byte checksum of a "FEVM" (f410f) address. | |
func Checksum(payload []byte) []byte { | |
hasher, err := blake2b.New(4, nil) | |
if err != nil { | |
panic(err) // guaranteed to not fail | |
} | |
// First hash the payload prefix: protocol (4) and namespace (10). | |
hasher.Write(fevmPrefixBytes) | |
// Then the payload itself. | |
_, _ = hasher.Write(payload) // guaranteed to never fail | |
return hasher.Sum(nil) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment