Skip to content

Instantly share code, notes, and snippets.

@Stebalien
Created March 10, 2023 02:32
Show Gist options
  • Save Stebalien/fd29d46eb24bcb72fe781cf5a09e7aa7 to your computer and use it in GitHub Desktop.
Save Stebalien/fd29d46eb24bcb72fe781cf5a09e7aa7 to your computer and use it in GitHub Desktop.
Example f4/0x conversion code
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