Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi-ecosystem derivation and sui addresses #5

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
69 changes: 29 additions & 40 deletions deposit_addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,16 @@ import (
"hash"

"github.com/btcsuite/btcd/chaincfg"
eth "github.com/ethereum/go-ethereum/common"
"github.com/lombard-finance/ledger-utils/address"
"github.com/lombard-finance/ledger-utils/chainid"
"github.com/pkg/errors"
)

type Address = eth.Address
type Sha256 = hash.Hash

// Chain type tags
//
// These tags are used to distinguish deposit addresses for different chain types
const (
ChainIdSize int = 32
EvmTag uint8 = 0
// TODO define more chain-type identifiers
)

const (
DepositAddrTag = "LombardDepositAddr"
DepositAddrTag = "LombardDepositAddr"
DeprecatedChainTag = byte(0)
)

// Create a tagged hasher used to compute Lombard deposit addresses
Expand All @@ -44,66 +36,63 @@ func depositHasher() Sha256 {
return h
}

// EvmDepositTweak Compute the tweak bytes for an EVM deposit address.
// DepositTweak Compute the tweak bytes for a deposit address.
//
// This is defined as
// This is generally defined as
//
// taggedHash( AuxData || EvmTag || ChainId || LBTCAddress || WalletAddress )
// taggedHash( AuxData || ChainId || LBTCAddress || WalletAddress )
Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. by the fact the byte is still there
  2. mb LChainId is better to show that it's something custom
Suggested change
// taggedHash( AuxData || ChainId || LBTCAddress || WalletAddress )
// taggedHash( AuxData || DeprecatedChainTag || LChainId || LBTCAddress || WalletAddress )

//
// where 'taggedHash' is a sha256 instance as returned by 'depositHasher()',
// 'EvmTag' is defined above, 'ChainId' is serialized as 32 big-endian bytes,
// LBTCAddress and WalletAddress are 20-byte EVM addresses, and AuxData is a
// 32-byte value encoding chain-agnostic auxiliary data.
func EvmDepositTweak(lbtcContract, wallet Address, chainId, auxData []byte) ([]byte, error) {
// where 'taggedHash' is a sha256 instance as returned by 'depositHasher()', 'ChainId' is a 32 bytes
// big-endian identifier of the chain, LBTCAddress and WalletAddress are byte arrays representing
// the respective addresses on the selected chain, and AuxData is a 32-byte value encoding
// chain-agnostic auxiliary data.
func DepositTweak(lbtcContract, wallet address.Address, chainId chainid.LChainId, auxData []byte) ([]byte, error) {
if len(auxData) != AuxDataSize {
return nil, errors.Errorf("wrong size for auxData (got %v, want %v)", len(auxData), AuxDataSize)
}
if len(chainId) != ChainIdSize {
return nil, errors.Errorf("wrong size for chainId (got %v, want %v)", len(chainId), ChainIdSize)
}

h := depositHasher()

// aux data (32 bytes)
h.Write(auxData[:])

// EVM tag (1 byte)
h.Write([]byte{EvmTag})
// 1 byte tag previously used to select chain, now deprecated and constant
// for backward compatibility
h.Write([]byte{DeprecatedChainTag})

// EVM chain-id (32 bytes)
// we zero-pad if `chainId` is less than 32 bytes and error if it is more.
h.Write(chainId[:])
// chain-id (32 bytes) as defined by Lombard documentation
h.Write(chainId.Bytes())

// LBTC contract address (20 bytes)
// LBTC contract address
h.Write(lbtcContract.Bytes())

// Destination wallet address (20 bytes)
// Destination wallet address
h.Write(wallet.Bytes())

return h.Sum(nil), nil
}

// EvmDepositSegwitPubkey Compute the segwit public key to be used for an EVM deposit.
// DepositSegwitPubkey Compute the segwit public key to be used for a deposit.
//
// - 'pk' is the base (untweaked) public key to tweak
// - 'lbtcContract' is the EVM address of the destination LBTC bridge contract
// - 'wallet' is the EVM address that will claim this deposit
// - 'chainId' is the chain id for the target EVM chain
func EvmDepositSegwitPubkey(pk *PublicKey, lbtcContract, wallet Address, chainId, auxData []byte) (*PublicKey, error) {
// - 'lbtcContract' is the address of the LBTC contract or object on the destination chain
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// - 'lbtcContract' is the address of the LBTC contract or object on the destination chain
// - 'lbtcContract' is the address of the LBTC contract (EVM) or object (SUI) on the destination chain

// - 'wallet' is the address that will claim the deposit on the destination chain
// - 'chainId' is the chain id for the target chain as defined in the Lombard documentation
func DepositSegwitPubkey(pk *PublicKey, lbtcContract, wallet address.Address, chainId chainid.LChainId, auxData []byte) (*PublicKey, error) {
// compute tweak bytes
tweakBytes, err := EvmDepositTweak(lbtcContract, wallet, chainId, auxData)
tweakBytes, err := DepositTweak(lbtcContract, wallet, chainId, auxData)
if err != nil {
return nil, err
}

return TweakPublicKey(pk, tweakBytes)
}

// EvmDepositSegwitAddr Compute the segwit deposit address to be used for an EVM deposit.
// See EvmDepositSegwitPubkey doc for argument descriptions.
func EvmDepositSegwitAddr(pk *PublicKey, bridge, wallet Address, chainId, auxData []byte, net *chaincfg.Params) (string, error) {
// DepositSegwitAddr Compute the segwit deposit address to be used for a deposit on the specified chain.
// See depositSegwitPubkey doc for argument descriptions.
func DepositSegwitAddr(pk *PublicKey, bridge, wallet address.Address, chainId chainid.LChainId, auxData []byte, net *chaincfg.Params) (string, error) {
// compute the pubkey
tpk, err := EvmDepositSegwitPubkey(pk, bridge, wallet, chainId, auxData)
tpk, err := DepositSegwitPubkey(pk, bridge, wallet, chainId, auxData)
if err != nil {
return "", err
}
Expand Down
120 changes: 111 additions & 9 deletions deposit_addr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"testing"

"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lombard-finance/ledger-utils/address"
"github.com/lombard-finance/ledger-utils/chainid"
"github.com/stretchr/testify/require"

"github.com/btcsuite/btcd/chaincfg"
eth "github.com/ethereum/go-ethereum/common"
)

type TestVal struct {
Expand Down Expand Up @@ -194,29 +197,32 @@ func TestEthTweakValueRustKat(t *testing.T) {
v4 := sha256.Sum256(v3[:])
hashVal = v4

lbtcContractAddr := eth.BytesToAddress(v1[:20])
walletAddr := eth.BytesToAddress(v2[:20])
lbtcContract, err := address.NewEvmAddress(v1[:20])
require.NoError(t, err, "error in test configuration for lbtc contract")
wallet, err := address.NewEvmAddress(v2[:20])
require.NoError(t, err, "error in test configuration for wallet")
chainIdU64 := binary.BigEndian.Uint64(v3[:8])
var chainId [32]byte
binary.BigEndian.PutUint64(chainId[24:], chainIdU64)
var chainIdBytes [32]byte
binary.BigEndian.PutUint64(chainIdBytes[24:], chainIdU64)
chainId, _ := chainid.NewLChainId(chainIdBytes[:])
auxData := v4

// check tweak result
tweak, err := EvmDepositTweak(lbtcContractAddr, walletAddr, chainId[:], auxData[:])
tweak, err := DepositTweak(lbtcContract, wallet, chainId, auxData[:])
if err != nil {
panic(fmt.Sprintf("error computing deposit tweak: %v", err))
}
tweakString := hex.EncodeToString(tweak)

// check deposit pubkey result
tpk, err := EvmDepositSegwitPubkey(pk, lbtcContractAddr, walletAddr, chainId[:], auxData[:])
tpk, err := DepositSegwitPubkey(pk, lbtcContract, wallet, chainId, auxData[:])
if err != nil {
panic(fmt.Sprintf("error tweaking pubkey: %v", err))
}
tpkString := hex.EncodeToString(tpk.SerializeCompressed())

// check segwit address
segwitAddr, err := EvmDepositSegwitAddr(pk, lbtcContractAddr, walletAddr, chainId[:], auxData[:], params)
segwitAddr, err := DepositSegwitAddr(pk, lbtcContract, wallet, chainId, auxData[:], params)
if err != nil {
panic(fmt.Sprintf("error tweaking addr: %v", err))
}
Expand All @@ -237,3 +243,99 @@ func TestEthTweakValueRustKat(t *testing.T) {
}
}
}

var referenceValues = []struct {
testLabel string
rootDepositKey string
auxData string
lbtcContract string
wallet string
chainId string
expectedTweak string
expectedPubkey string
expectedSegwitAddr string
btcParams chaincfg.Params
}{
{
testLabel: "Sui Testnet - 1",
rootDepositKey: "0x043dcf7a68429b23a0396ca61c1ab243ccbbcc629ff04c59394458d6db5dd2bb159e0b7a71ef07247b59a0a21b1f1eaee61a40064ade423e926f38550065a43587",
auxData: "2137aefeb756a435f07fceff39a061bd2a062b617bd8857e9c32b44ef2596bc8", // ComputeAuxDataV0(0, [32]byte{0...})
lbtcContract: "54945cd3d15c0012d35a92ed6f1f373157216fc6bdc5bd79b03ee86da3ca455b",
wallet: "0d3c73069aef96e8a1d209e2c96ddefc4b911d025932e414db201be70f0ae15e",
chainId: "010000000000000000000000000000000000000000000000000000004c78adac",
expectedTweak: "c5f14fe401d015c4ea34632e6d775b751e1925fe718e6b339d2a74689b3a0609",
expectedPubkey: "02a2188a8c2449e16c3f50aab677c8be90916a7c7e6ab25a88222fc846257c28fb",
expectedSegwitAddr: "tb1q9sjdz0vpnsshhule5kkxvfggvq8lznpck4tay0",
btcParams: chaincfg.SigNetParams,
},
{
testLabel: "Sui Testnet - 2",
rootDepositKey: "0x043dcf7a68429b23a0396ca61c1ab243ccbbcc629ff04c59394458d6db5dd2bb159e0b7a71ef07247b59a0a21b1f1eaee61a40064ade423e926f38550065a43587",
auxData: "2137aefeb756a435f07fceff39a061bd2a062b617bd8857e9c32b44ef2596bc8", // ComputeAuxDataV0(0, [32]byte{0...})
lbtcContract: "54945cd3d15c0012d35a92ed6f1f373157216fc6bdc5bd79b03ee86da3ca455b",
wallet: "5e9ae2ae1c76cb14be16cd2d521f8200c95cc94ab30947c61ade11a0a6439d28",
chainId: "010000000000000000000000000000000000000000000000000000004c78adac",
expectedTweak: "a6d4eb9bcfa5c683513b06fd531184792767f5355402ce23c6dab417696f6392",
expectedPubkey: "0393e2e2e1acc9a702d8b62e990ed9eff4e36589c6cd44e48101a2d3c3c58d5abb",
expectedSegwitAddr: "tb1q8uwc6au5765r9jttj949dg6f2qzcqt58svy8c0",
btcParams: chaincfg.SigNetParams,
},
{
testLabel: "Sui Mainnet - 1",
rootDepositKey: "0x043dcf7a68429b23a0396ca61c1ab243ccbbcc629ff04c59394458d6db5dd2bb159e0b7a71ef07247b59a0a21b1f1eaee61a40064ade423e926f38550065a43587",
auxData: "2137aefeb756a435f07fceff39a061bd2a062b617bd8857e9c32b44ef2596bc8", // ComputeAuxDataV0(0, [32]byte{0...})
lbtcContract: "54945cd3d15c0012d35a92ed6f1f373157216fc6bdc5bd79b03ee86da3ca455b",
wallet: "0d3c73069aef96e8a1d209e2c96ddefc4b911d025932e414db201be70f0ae15e",
chainId: "0100000000000000000000000000000000000000000000000000000035834a8a",
expectedTweak: "35b7205e7d5f1b077091f3164e5daed121f4bb27799c57d9acd976a4044a18bd",
expectedPubkey: "02744518bc0dafc22c494f7dc9ec780fa6d9ae53d3be720ee003f672edfba1063b",
expectedSegwitAddr: "bc1qk5xk9gfr5r8l57ma2euyyvc8h7kc9etlfz2w98",
btcParams: chaincfg.MainNetParams,
},
{
testLabel: "Sui Mainnet - 2",
rootDepositKey: "0x043dcf7a68429b23a0396ca61c1ab243ccbbcc629ff04c59394458d6db5dd2bb159e0b7a71ef07247b59a0a21b1f1eaee61a40064ade423e926f38550065a43587",
auxData: "2137aefeb756a435f07fceff39a061bd2a062b617bd8857e9c32b44ef2596bc8", // ComputeAuxDataV0(0, [32]byte{0...})
lbtcContract: "54945cd3d15c0012d35a92ed6f1f373157216fc6bdc5bd79b03ee86da3ca455b",
wallet: "5e9ae2ae1c76cb14be16cd2d521f8200c95cc94ab30947c61ade11a0a6439d28",
chainId: "0100000000000000000000000000000000000000000000000000000035834a8a",
expectedTweak: "e454f631af7c2f235c372be61a63c58a1b94832e0240cf1a06b9e657df5d9c13",
expectedPubkey: "034a915f6ac8d6c6920a754392338aa2f41a4070d30564d6af3749d80a9b58eb81",
expectedSegwitAddr: "bc1qagvmd7y5x5hkkn6avva5mv0thhlnn6ktuh5ppd",
btcParams: chaincfg.MainNetParams,
},
}

func TestWithReferenceValues(t *testing.T) {
// just an initial seed to generate constant data for all tests
hashVal := sha256.Sum256([]byte("segwit_lombard_tweak_test_rs"))
pk := secp256k1.PrivKeyFromBytes(hashVal[:]).PubKey()
Comment on lines +310 to +312

Choose a reason for hiding this comment

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

shouldn't you read this public key from referenceValues (e.g., from the rootDepositKey field) so that the reference values are self-contained?

Choose a reason for hiding this comment

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

@russanto @hashxtree any thoughts here? Thanks so much!


for _, rf := range referenceValues {
t.Run(rf.testLabel, func(t *testing.T) {
lbtcContract, err := address.NewSuiAddressFromHex(rf.lbtcContract)
require.NoError(t, err)
wallet, err := address.NewSuiAddressFromHex(rf.wallet)
require.NoError(t, err)
chainId, err := chainid.NewLChainIdFromHex(rf.chainId)
require.NoError(t, err)
auxDataBytes, err := hex.DecodeString(rf.auxData)
require.NoError(t, err)

// check tweak result
tweak, err := DepositTweak(lbtcContract, wallet, chainId, auxDataBytes)
require.NoError(t, err, "error on deposit tweak calculation")
require.Equal(t, rf.expectedTweak, hex.EncodeToString(tweak))

// check deposit pubkey result
tpk, err := DepositSegwitPubkey(pk, lbtcContract, wallet, chainId, auxDataBytes)
require.NoError(t, err, "error tweaking the public key")
require.Equal(t, rf.expectedPubkey, hex.EncodeToString(tpk.SerializeCompressed()))

// check segwit address
segwitAddr, err := DepositSegwitAddr(pk, lbtcContract, wallet, chainId, auxDataBytes, &rf.btcParams)
require.NoError(t, err, "error deriving address")
require.Equal(t, rf.expectedSegwitAddr, segwitAddr)
})
}
}
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/btcsuite/btcd v0.24.0
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
github.com/ethereum/go-ethereum v1.14.5
github.com/lombard-finance/ledger-utils v0.2.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
)
Expand All @@ -15,7 +15,6 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/sys v0.20.0 // indirect
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/ethereum/go-ethereum v1.14.5 h1:szuFzO1MhJmweXjoM5nSAeDvjNUH3vIQoMzzQnfvjpw=
github.com/ethereum/go-ethereum v1.14.5/go.mod h1:VEDGGhSxY7IEjn98hJRFXl/uFvpRgbIIf2PpXiyGGgc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand All @@ -50,13 +48,13 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/lombard-finance/ledger-utils v0.2.0 h1:nrIg5g9tu3jH1ViAnvMXqpA00cIs/CjkqU2Rb7Ujqv8=
github.com/lombard-finance/ledger-utils v0.2.0/go.mod h1:a61awHF3EakbFArqyz8UaQt120DUJH3wEOaI671SThs=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down
2 changes: 2 additions & 0 deletions segwit_tweak_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/hex"
"fmt"
"testing"

"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

// known-answer test values generated from reference rust impl
Expand Down
48 changes: 21 additions & 27 deletions tweak_bytes.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
package deposit_address

import (
eth "github.com/ethereum/go-ethereum/common"
"github.com/lombard-finance/ledger-utils/address"
"github.com/lombard-finance/ledger-utils/chainid"
"github.com/pkg/errors"
)

type BlockchainType string

const (
BlockchainTypeEvm BlockchainType = "evm"
)

// CalcTweakBytes Compute the tweakBytes for a given request, dispatching on `blockchainType`
func CalcTweakBytes(
blockchainType BlockchainType,
chainId [32]byte,
toAddress, lbtcAddress, auxData []byte,
chainId chainid.LChainId,
toAddress, lbtcAddress address.Address,
auxData []byte,
) ([]byte, error) {

switch blockchainType {
case BlockchainTypeEvm:
// evm chain uses 20-byte address
if len(lbtcAddress) != 20 {
return nil, errors.Errorf("bad LbtcAddress (got %d bytes, expected 20)", len(lbtcAddress))
}

lbtcAddr := eth.BytesToAddress(lbtcAddress)
if len(toAddress) != 20 {
return nil, errors.Errorf("bad ToAddress (got %d bytes, expected 20)", len(toAddress))
}

depositAddr := eth.BytesToAddress(toAddress)
return EvmDepositTweak(lbtcAddr, depositAddr, chainId[:], auxData)
default:
return nil, errors.Errorf("unsupported blockchain type: %s", blockchainType)
if chainId.Ecosystem() != toAddress.Ecosystem() {
return nil, errors.Errorf(
"ecosystem mismatch between chain (%s) and to address (%s:%s)",
chainId.Ecosystem().String(),
toAddress.Ecosystem().String(),
toAddress.String(),
)
}
if chainId.Ecosystem() != lbtcAddress.Ecosystem() {
return nil, errors.Errorf(
"ecosystem mismatch between chain (%s) and LBTC address (%s:%s)",
chainId.Ecosystem().String(),
lbtcAddress.Ecosystem().String(),
lbtcAddress.String(),
)
}
return DepositTweak(lbtcAddress, toAddress, chainId, auxData)
}