feat: dasigners precompile

This commit is contained in:
MiniFrenchBread 2025-03-27 06:28:19 +08:00
parent eca91aa939
commit c8b9013ac6
31 changed files with 6574 additions and 1 deletions

3
.gitignore vendored
View file

@ -43,3 +43,6 @@ profile.cov
.vscode .vscode
tests/spec-tests/ tests/spec-tests/
# Precompiles
core/vm/Precompiles/interfaces/node_modules

View file

@ -0,0 +1,166 @@
package bn254util
import (
"math/big"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/ethereum/go-ethereum/crypto"
)
const (
G1PointSize = 32 * 2
G2PointSize = 32 * 2 * 2
)
var (
FR_MODULUS, _ = new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10)
)
func VerifySig(sig *bn254.G1Affine, pubkey *bn254.G2Affine, msgBytes [32]byte) (bool, error) {
g2Gen := GetG2Generator()
msgPoint := MapToCurve(msgBytes)
var negSig bn254.G1Affine
negSig.Neg((*bn254.G1Affine)(sig))
P := [2]bn254.G1Affine{*msgPoint, negSig}
Q := [2]bn254.G2Affine{*pubkey, *g2Gen}
ok, err := bn254.PairingCheck(P[:], Q[:])
if err != nil {
return false, nil
}
return ok, nil
}
func MapToCurve(digest [32]byte) *bn254.G1Affine {
one := new(big.Int).SetUint64(1)
three := new(big.Int).SetUint64(3)
x := new(big.Int)
x.SetBytes(digest[:])
for {
// y = x^3 + 3
xP3 := new(big.Int).Exp(x, big.NewInt(3), fp.Modulus())
y := new(big.Int).Add(xP3, three)
y.Mod(y, fp.Modulus())
if y.ModSqrt(y, fp.Modulus()) == nil {
x.Add(x, one).Mod(x, fp.Modulus())
} else {
var fpX, fpY fp.Element
fpX.SetBigInt(x)
fpY.SetBigInt(y)
return &bn254.G1Affine{
X: fpX,
Y: fpY,
}
}
}
}
func CheckG1AndG2DiscreteLogEquality(pointG1 *bn254.G1Affine, pointG2 *bn254.G2Affine) (bool, error) {
negGenG1 := new(bn254.G1Affine).Neg(GetG1Generator())
return bn254.PairingCheck([]bn254.G1Affine{*pointG1, *negGenG1}, []bn254.G2Affine{*GetG2Generator(), *pointG2})
}
func GetG1Generator() *bn254.G1Affine {
g1Gen := new(bn254.G1Affine)
_, err := g1Gen.X.SetString("1")
if err != nil {
return nil
}
_, err = g1Gen.Y.SetString("2")
if err != nil {
return nil
}
return g1Gen
}
func GetG2Generator() *bn254.G2Affine {
g2Gen := new(bn254.G2Affine)
g2Gen.X.SetString("10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634")
g2Gen.Y.SetString("8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531")
return g2Gen
}
func MulByGeneratorG1(a *fr.Element) *bn254.G1Affine {
g1Gen := GetG1Generator()
return new(bn254.G1Affine).ScalarMultiplication(g1Gen, a.BigInt(new(big.Int)))
}
func MulByGeneratorG2(a *fr.Element) *bn254.G2Affine {
g2Gen := GetG2Generator()
return new(bn254.G2Affine).ScalarMultiplication(g2Gen, a.BigInt(new(big.Int)))
}
func SerializeG1(p *bn254.G1Affine) []byte {
b := make([]byte, 0)
tmp := p.X.Bytes()
for i := 0; i < 32; i++ {
b = append(b, tmp[i])
}
tmp = p.Y.Bytes()
for i := 0; i < 32; i++ {
b = append(b, tmp[i])
}
return b
}
func DeserializeG1(b []byte) *bn254.G1Affine {
p := new(bn254.G1Affine)
p.X.SetBytes(b[0:32])
p.Y.SetBytes(b[32:64])
return p
}
func SerializeG2(p *bn254.G2Affine) []byte {
b := make([]byte, 0)
tmp := p.X.A0.Bytes()
for i := 0; i < 32; i++ {
b = append(b, tmp[i])
}
tmp = p.X.A1.Bytes()
for i := 0; i < 32; i++ {
b = append(b, tmp[i])
}
tmp = p.Y.A0.Bytes()
for i := 0; i < 32; i++ {
b = append(b, tmp[i])
}
tmp = p.Y.A1.Bytes()
for i := 0; i < 32; i++ {
b = append(b, tmp[i])
}
return b
}
func DeserializeG2(b []byte) *bn254.G2Affine {
p := new(bn254.G2Affine)
p.X.A0.SetBytes(b[0:32])
p.X.A1.SetBytes(b[32:64])
p.Y.A0.SetBytes(b[64:96])
p.Y.A1.SetBytes(b[96:128])
return p
}
func Gamma(hash *bn254.G1Affine, signature *bn254.G1Affine, pkG1 *bn254.G1Affine, pkG2 *bn254.G2Affine) *big.Int {
toHash := make([]byte, 0)
toHash = append(toHash, SerializeG1(hash)...)
toHash = append(toHash, SerializeG1(signature)...)
toHash = append(toHash, SerializeG1(pkG1)...)
toHash = append(toHash, SerializeG2(pkG2)...)
msgHash := crypto.Keccak256(toHash)
gamma := new(big.Int)
gamma.SetBytes(msgHash)
gamma.Mod(gamma, FR_MODULUS)
return gamma
}

View file

@ -19,6 +19,7 @@ package common
import ( import (
"bytes" "bytes"
"database/sql/driver" "database/sql/driver"
"encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
@ -486,3 +487,9 @@ func (b PrettyBytes) TerminalString() string {
} }
return fmt.Sprintf("%#x...%x (%dB)", b[:3], b[len(b)-3:], len(b)) return fmt.Sprintf("%#x...%x (%dB)", b[:3], b[len(b)-3:], len(b))
} }
func Uint64ToBytes(i uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, i)
return b
}

View file

@ -29,6 +29,7 @@ import (
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -68,6 +69,43 @@ func WithCustomPrecompiles(base PrecompiledContracts) PrecompiledContracts {
return result return result
} }
// PrecompiledContract is the basic interface for custom precompile contracts.
type StatefulPrecompiledContract interface {
PrecompiledContract
Abi() *abi.ABI
IsTx(string) bool
}
func InitializeStatefulPrecompileCall(
p StatefulPrecompiledContract,
evm *EVM,
contract *Contract,
readonly bool,
) (
method *abi.Method,
args []interface{},
err error,
) {
// parse input
if len(contract.Input) < 4 {
return nil, nil, ErrExecutionReverted
}
method, err = p.Abi().MethodById(contract.Input[:4])
if err != nil {
return nil, nil, ErrExecutionReverted
}
args, err = method.Inputs.Unpack(contract.Input[4:])
if err != nil {
return nil, nil, ErrExecutionReverted
}
// readonly check
if readonly && p.IsTx(method.Name) {
return nil, nil, ErrExecutionReverted
}
return method, args, nil
}
// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
// contracts used in the Frontier and Homestead releases. // contracts used in the Frontier and Homestead releases.
var PrecompiledContractsHomestead = WithCustomPrecompiles(PrecompiledContracts{ var PrecompiledContractsHomestead = WithCustomPrecompiles(PrecompiledContracts{

633
core/vm/dasigners.go Normal file
View file

@ -0,0 +1,633 @@
package vm
import (
"bytes"
"math/big"
"sort"
"strings"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/bn254util"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm/precompiles"
"github.com/ethereum/go-ethereum/core/vm/precompiles/dasigners"
"github.com/ethereum/go-ethereum/crypto"
"github.com/vmihailenco/msgpack/v5"
)
const (
requiredGasMax uint64 = 1000_000_000
DASignersFunctionParams = "params"
DASignersFunctionEpochNumber = "epochNumber"
DASignersFunctionQuorumCount = "quorumCount"
DASignersFunctionGetSigner = "getSigner"
DASignersFunctionGetQuorum = "getQuorum"
DASignersFunctionGetQuorumRow = "getQuorumRow"
DASignersFunctionRegisterSigner = "registerSigner"
DASignersFunctionUpdateSocket = "updateSocket"
DASignersFunctionRegisterNextEpoch = "registerNextEpoch"
DASignersFunctionGetAggPkG1 = "getAggPkG1"
DASignersFunctionIsSigner = "isSigner"
DASignersFunctionRegisteredEpoch = "registeredEpoch"
DASignersFunctionMakeEpoch = "makeEpoch"
)
var RequiredGasBasic = map[string]uint64{
DASignersFunctionParams: 1_000,
DASignersFunctionEpochNumber: 1_000,
DASignersFunctionQuorumCount: 1_000,
DASignersFunctionGetSigner: 100_000,
DASignersFunctionGetQuorum: 100_000,
DASignersFunctionGetQuorumRow: 10_000,
DASignersFunctionRegisterSigner: 100_000,
DASignersFunctionUpdateSocket: 50_000,
DASignersFunctionRegisterNextEpoch: 100_000,
DASignersFunctionGetAggPkG1: 1_000_000,
DASignersFunctionIsSigner: 10_000,
DASignersFunctionRegisteredEpoch: 10_000,
DASignersFunctionMakeEpoch: 100_000,
}
const (
NewSignerEvent = "NewSigner"
SocketUpdatedEvent = "SocketUpdated"
)
var _ StatefulPrecompiledContract = &DASignersPrecompile{}
type DASignersPrecompile struct {
abi abi.ABI
}
func NewDASignersPrecompile() (*DASignersPrecompile, error) {
abi, err := abi.JSON(strings.NewReader(dasigners.DASignersABI))
if err != nil {
return nil, err
}
return &DASignersPrecompile{
abi: abi,
}, nil
}
// Address implements vm.PrecompiledContract.
func (d *DASignersPrecompile) Address() common.Address {
return common.HexToAddress("0x0000000000000000000000000000000000001000")
}
// RequiredGas implements vm.PrecompiledContract.
func (d *DASignersPrecompile) RequiredGas(input []byte) uint64 {
method, err := d.abi.MethodById(input[:4])
if err != nil {
return requiredGasMax
}
if gas, ok := RequiredGasBasic[method.Name]; ok {
return gas
}
return requiredGasMax
}
func (d *DASignersPrecompile) IsTx(method string) bool {
switch method {
case DASignersFunctionUpdateSocket,
DASignersFunctionRegisterSigner,
DASignersFunctionRegisterNextEpoch:
return true
default:
return false
}
}
func (d *DASignersPrecompile) Abi() *abi.ABI {
return &d.abi
}
// Run implements vm.PrecompiledContract.
func (d *DASignersPrecompile) Run(evm *EVM, contract *Contract, readonly bool) ([]byte, error) {
method, args, err := InitializeStatefulPrecompileCall(d, evm, contract, readonly)
if err != nil {
return nil, err
}
var bz []byte
switch method.Name {
// queries
case DASignersFunctionParams:
bz, err = d.Params(evm, method, args)
case DASignersFunctionEpochNumber:
bz, err = d.EpochNumber(evm, method, args)
case DASignersFunctionQuorumCount:
bz, err = d.QuorumCount(evm, method, args)
case DASignersFunctionGetSigner:
bz, err = d.GetSigner(evm, method, args)
case DASignersFunctionGetQuorum:
bz, err = d.GetQuorum(evm, method, args)
case DASignersFunctionGetQuorumRow:
bz, err = d.GetQuorumRow(evm, method, args)
case DASignersFunctionGetAggPkG1:
bz, err = d.GetAggPkG1(evm, method, args)
case DASignersFunctionIsSigner:
bz, err = d.IsSigner(evm, method, args)
case DASignersFunctionRegisteredEpoch:
bz, err = d.RegisteredEpoch(evm, method, args)
// txs
case DASignersFunctionRegisterSigner:
bz, err = d.RegisterSigner(evm, contract, method, args)
case DASignersFunctionRegisterNextEpoch:
bz, err = d.RegisterNextEpoch(evm, contract, method, args)
case DASignersFunctionUpdateSocket:
bz, err = d.UpdateSocket(evm, contract, method, args)
case DASignersFunctionMakeEpoch:
bz, err = d.MakeEpoch(evm, contract, method, args)
}
if err != nil {
return nil, err
}
return bz, nil
}
func (d *DASignersPrecompile) EmitNewSignerEvent(evm *EVM, signer dasigners.IDASignersSignerDetail) error {
event := d.abi.Events[NewSignerEvent]
quries := make([]interface{}, 2)
quries[0] = event.ID
quries[1] = signer.Signer
topics, err := abi.MakeTopics(quries)
if err != nil {
return err
}
arguments := abi.Arguments{event.Inputs[1], event.Inputs[2]}
b, err := arguments.Pack(signer.PkG1, signer.PkG2)
if err != nil {
return err
}
evm.StateDB.AddLog(&types.Log{
Address: d.Address(),
Topics: topics[0],
Data: b,
BlockNumber: evm.Context.BlockNumber.Uint64(),
})
return d.EmitSocketUpdatedEvent(evm, signer.Signer, signer.Socket)
}
func (d *DASignersPrecompile) EmitSocketUpdatedEvent(evm *EVM, signer common.Address, socket string) error {
event := d.abi.Events[SocketUpdatedEvent]
quries := make([]interface{}, 2)
quries[0] = event.ID
quries[1] = signer
topics, err := abi.MakeTopics(quries)
if err != nil {
return err
}
arguments := abi.Arguments{event.Inputs[1]}
b, err := arguments.Pack(socket)
if err != nil {
return err
}
evm.StateDB.AddLog(&types.Log{
Address: d.Address(),
Topics: topics[0],
Data: b,
BlockNumber: evm.Context.BlockNumber.Uint64(),
})
return nil
}
type Ballot struct {
account common.Address
content []byte
}
func (d *DASignersPrecompile) MakeEpoch(
evm *EVM,
contract *Contract,
method *abi.Method,
args []interface{},
) ([]byte, error) {
if len(args) != 0 {
return nil, ErrExecutionReverted
}
params := d.params()
epoch := d.epochNumber(evm)
epochBlock := d.epochBlock(evm, epoch)
blockHeight := evm.Context.BlockNumber.Uint64()
if epochBlock > 0 && blockHeight < epochBlock+params.EpochBlocks.Uint64() {
// not yet to the next epoch
return method.Outputs.Pack()
}
// new epoch
epoch += 1
cnt := d.epochRegistration(evm, epoch)
ballots := make([]Ballot, cnt)
for index := range cnt {
account := d.epochRegisteredSigner(evm, epoch, index)
sigHash, _ := d.getRegistration(evm, epoch, account)
ballots[index] = Ballot{
account: account,
content: sigHash,
}
}
// TODO: calculate ballots based on staked amount
sort.Slice(ballots, func(i, j int) bool {
return bytes.Compare(ballots[i].content, ballots[j].content) < 0
})
quorums := make([][]common.Address, 0)
encodedSlices := params.EncodedSlices.Uint64()
maxQuorums := params.MaxQuorums.Uint64()
if len(ballots) >= int(encodedSlices) {
for i := 0; i+int(encodedSlices) <= len(ballots); i += int(encodedSlices) {
if int(maxQuorums) <= len(quorums) {
break
}
quorum := make([]common.Address, encodedSlices)
for j := 0; j < int(encodedSlices); j += 1 {
quorum[j] = ballots[i+j].account
}
quorums = append(quorums, quorum)
}
if len(ballots)%int(encodedSlices) != 0 && int(maxQuorums) > len(quorums) {
quorum := make([]common.Address, 0)
for j := len(ballots) - int(encodedSlices); j < len(ballots); j += 1 {
quorum = append(quorum, ballots[j].account)
}
quorums = append(quorums, quorum)
}
} else if len(ballots) > 0 {
quorum := make([]common.Address, encodedSlices)
n := len(ballots)
for i := 0; i < int(encodedSlices); i += 1 {
quorum[i] = ballots[i%n].account
}
quorums = append(quorums, quorum)
}
// save quorums
for index, quorum := range quorums {
b, err := msgpack.Marshal(quorum)
if err != nil {
return nil, err
}
StoreBytes(evm.StateDB, d.Address(), dasigners.QuorumKey(epoch+1, uint64(index)), b)
}
// save epoch number & block height
evm.StateDB.SetState(d.Address(), dasigners.EpochNumberKey(), common.BigToHash(big.NewInt(int64(epoch))))
evm.StateDB.SetState(d.Address(), dasigners.EpochBlockKey(epoch), common.BigToHash(big.NewInt(int64(blockHeight))))
return method.Outputs.Pack()
}
func (d *DASignersPrecompile) setSigner(evm *EVM, signer dasigners.IDASignersSignerDetail) error {
b, err := msgpack.Marshal(signer)
if err != nil {
return err
}
StoreBytes(evm.StateDB, d.Address(), dasigners.SignerKey(signer.Signer), b)
return nil
}
func (d *DASignersPrecompile) RegisterSigner(
evm *EVM,
contract *Contract,
method *abi.Method,
args []interface{},
) ([]byte, error) {
if len(args) != 2 {
return nil, ErrExecutionReverted
}
signer := args[0].(dasigners.IDASignersSignerDetail)
signature := dasigners.SerializeG1(args[1].(dasigners.BN254G1Point))
// validation
if evm.Origin != signer.Signer {
return nil, dasigners.ErrInvalidSender
}
if contract.caller != evm.Origin {
return nil, precompiles.ErrSenderNotOrigin
}
// execute
// validate sender
// TODO: check staked
_, found, err := d.getSigner(evm, signer.Signer)
if err != nil {
return nil, err
}
if found {
return nil, dasigners.ErrSignerExists
}
// validate signature
chainID := evm.chainConfig.ChainID
hash := dasigners.PubkeyRegistrationHash(signer.Signer, chainID)
if !dasigners.ValidateSignature(signer, hash, bn254util.DeserializeG1(signature)) {
return nil, dasigners.ErrInvalidSignature
}
// save signer
if err := d.setSigner(evm, signer); err != nil {
return nil, err
}
// emit events
err = d.EmitNewSignerEvent(evm, signer)
if err != nil {
return nil, err
}
return method.Outputs.Pack()
}
func (d *DASignersPrecompile) epochRegistration(evm *EVM, epoch uint64) uint64 {
return evm.StateDB.GetState(d.Address(), dasigners.EpochRegistrationKey(epoch)).Big().Uint64()
}
func (d *DASignersPrecompile) epochRegisteredSigner(evm *EVM, epoch uint64, index uint64) common.Address {
h := evm.StateDB.GetState(d.Address(), dasigners.EpochRegisteredSignerKey(epoch, index))
return common.Address(h[12:])
}
func (d *DASignersPrecompile) storeRegistration(evm *EVM, epoch uint64, signer common.Address, signature []byte) error {
if _, found := d.getRegistration(evm, epoch, signer); found {
return nil
}
// save signature hash
evm.StateDB.SetState(d.Address(), dasigners.RegistrationKey(epoch, signer), crypto.Keccak256Hash(signature))
// increment epoch registration count
registration := d.epochRegistration(evm, epoch)
evm.StateDB.SetState(d.Address(), dasigners.EpochRegistrationKey(epoch), common.BigToHash(big.NewInt(int64(registration+1))))
// save registered signer address
evm.StateDB.SetState(d.Address(), dasigners.EpochRegisteredSignerKey(epoch, registration), common.BytesToHash(signer.Bytes()))
return nil
}
func (d *DASignersPrecompile) RegisterNextEpoch(
evm *EVM,
contract *Contract,
method *abi.Method,
args []interface{},
) ([]byte, error) {
if len(args) != 1 {
return nil, ErrExecutionReverted
}
signature := dasigners.SerializeG1(args[0].(dasigners.BN254G1Point))
// validation
if contract.caller != evm.Origin {
return nil, precompiles.ErrSenderNotOrigin
}
// execute
// get signer
// TODO: check staked
signer, found, err := d.getSigner(evm, contract.caller)
if err != nil {
return nil, err
}
if !found {
return nil, dasigners.ErrSignerNotFound
}
// validate signature
epochNumber := d.epochNumber(evm)
chainID := evm.chainConfig.ChainID
hash := dasigners.EpochRegistrationHash(contract.caller, epochNumber+1, chainID)
if !dasigners.ValidateSignature(signer, hash, bn254util.DeserializeG1(signature)) {
return nil, dasigners.ErrInvalidSignature
}
// save registration
if err := d.storeRegistration(evm, epochNumber+1, contract.caller, signature); err != nil {
return nil, err
}
return method.Outputs.Pack()
}
func (d *DASignersPrecompile) UpdateSocket(
evm *EVM,
contract *Contract,
method *abi.Method,
args []interface{},
) ([]byte, error) {
if len(args) != 1 {
return nil, ErrExecutionReverted
}
socket := args[0].(string)
// validation
if contract.caller != evm.Origin {
return nil, precompiles.ErrSenderNotOrigin
}
// execute
signer, found, err := d.getSigner(evm, contract.caller)
if err != nil {
return nil, err
}
if !found {
return nil, dasigners.ErrSignerNotFound
}
signer.Socket = socket
if err := d.setSigner(evm, signer); err != nil {
return nil, err
}
// emit events
err = d.EmitSocketUpdatedEvent(evm, contract.caller, socket)
if err != nil {
return nil, err
}
return method.Outputs.Pack()
}
func (d *DASignersPrecompile) params() dasigners.IDASignersParams {
return dasigners.IDASignersParams{
TokensPerVote: big.NewInt(10),
MaxVotesPerSigner: big.NewInt(1024),
MaxQuorums: big.NewInt(10),
EpochBlocks: big.NewInt(5760),
EncodedSlices: big.NewInt(3072),
}
}
func (d *DASignersPrecompile) Params(evm *EVM, method *abi.Method, _ []interface{}) ([]byte, error) {
return method.Outputs.Pack(d.params())
}
func (d *DASignersPrecompile) epochBlock(evm *EVM, epoch uint64) uint64 {
return evm.StateDB.GetState(d.Address(), dasigners.EpochBlockKey(epoch)).Big().Uint64()
}
func (d *DASignersPrecompile) epochNumber(evm *EVM) uint64 {
return evm.StateDB.GetState(d.Address(), dasigners.EpochNumberKey()).Big().Uint64()
}
func (d *DASignersPrecompile) EpochNumber(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 0 {
return nil, ErrExecutionReverted
}
return method.Outputs.Pack(big.NewInt(int64(d.epochNumber(evm))))
}
func (d *DASignersPrecompile) quorumCount(evm *EVM, epochNumber uint64) uint64 {
return evm.StateDB.GetState(d.Address(), dasigners.QuorumCountKey(epochNumber)).Big().Uint64()
}
func (d *DASignersPrecompile) QuorumCount(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 1 {
return nil, ErrExecutionReverted
}
epochNumber := args[0].(*big.Int).Uint64()
return method.Outputs.Pack(big.NewInt(int64(d.quorumCount(evm, epochNumber))))
}
func (d *DASignersPrecompile) getSigner(evm *EVM, account common.Address) (dasigners.IDASignersSignerDetail, bool, error) {
b := LoadBytes(evm.StateDB, d.Address(), dasigners.SignerKey(account))
if len(b) == 0 {
return dasigners.IDASignersSignerDetail{}, false, nil
}
var signer dasigners.IDASignersSignerDetail
err := msgpack.Unmarshal(b, &signer)
if err != nil {
return dasigners.IDASignersSignerDetail{}, false, err
}
return signer, false, nil
}
func (d *DASignersPrecompile) GetSigner(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 1 {
return nil, ErrExecutionReverted
}
accounts := args[0].([]common.Address)
signers := make([]dasigners.IDASignersSignerDetail, len(accounts))
for i, account := range accounts {
signer, found, err := d.getSigner(evm, account)
if err != nil {
return nil, err
}
if !found {
return nil, dasigners.ErrSignerNotFound
}
signers[i] = signer
}
return method.Outputs.Pack(signers)
}
func (d *DASignersPrecompile) IsSigner(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 1 {
return nil, ErrExecutionReverted
}
account := args[0].(common.Address)
_, found, err := d.getSigner(evm, account)
if err != nil {
return nil, err
}
return method.Outputs.Pack(found)
}
func (d *DASignersPrecompile) getRegistration(evm *EVM, epoch uint64, account common.Address) ([]byte, bool) {
h := evm.StateDB.GetState(d.Address(), dasigners.RegistrationKey(epoch, account))
if h == (common.Hash{}) {
return nil, false
}
return h.Bytes(), true
}
func (d *DASignersPrecompile) RegisteredEpoch(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 2 {
return nil, ErrExecutionReverted
}
account := args[0].(common.Address)
epoch := args[1].(*big.Int).Uint64()
_, found := d.getRegistration(evm, epoch, account)
return method.Outputs.Pack(found)
}
func (d *DASignersPrecompile) getQuorum(evm *EVM, epochNumber uint64, quorumId uint64) ([]common.Address, error) {
if d.quorumCount(evm, epochNumber) <= quorumId {
return nil, dasigners.ErrQuorumIdOutOfBound
}
if d.epochNumber(evm) < epochNumber {
return nil, dasigners.ErrEpochOutOfBound
}
b := LoadBytes(evm.StateDB, d.Address(), dasigners.QuorumKey(epochNumber, quorumId))
var quorum []common.Address
err := msgpack.Unmarshal(b, &quorum)
if err != nil {
return nil, err
}
return quorum, nil
}
func (d *DASignersPrecompile) GetQuorum(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 2 {
return nil, ErrExecutionReverted
}
epochNumber := args[0].(*big.Int).Uint64()
quorumId := args[1].(*big.Int).Uint64()
quorum, err := d.getQuorum(evm, epochNumber, quorumId)
if err != nil {
return nil, err
}
return method.Outputs.Pack(quorum)
}
func (d *DASignersPrecompile) GetQuorumRow(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 3 {
return nil, ErrExecutionReverted
}
epochNumber := args[0].(*big.Int).Uint64()
quorumId := args[1].(*big.Int).Uint64()
rowIndex := args[2].(uint32)
quorum, err := d.getQuorum(evm, epochNumber, quorumId)
if err != nil {
return nil, err
}
if int(rowIndex) >= len(quorum) {
return nil, dasigners.ErrRowIdOfBound
}
return method.Outputs.Pack(quorum[rowIndex])
}
func (d *DASignersPrecompile) getAggPkG1(
evm *EVM,
epochNumber uint64,
quorumId uint64,
quorumBitmap []byte,
) (dasigners.BN254G1Point, *big.Int, *big.Int, error) {
quorum, err := d.getQuorum(evm, epochNumber, quorumId)
if err != nil {
return dasigners.BN254G1Point{}, nil, nil, err
}
if (len(quorum)+7)/8 != len(quorumBitmap) {
return dasigners.BN254G1Point{}, nil, nil, dasigners.ErrQuorumBitmapLengthMismatch
}
aggPubkeyG1 := new(bn254.G1Affine)
hit := 0
added := make(map[common.Address]struct{})
for i, signer := range quorum {
if _, ok := added[signer]; ok {
hit += 1
continue
}
b := quorumBitmap[i/8] & (1 << (i % 8))
if b == 0 {
continue
}
hit += 1
added[signer] = struct{}{}
signer, found, err := d.getSigner(evm, signer)
if err != nil {
return dasigners.BN254G1Point{}, nil, nil, err
}
if !found {
return dasigners.BN254G1Point{}, nil, nil, dasigners.ErrSignerNotFound
}
aggPubkeyG1.Add(aggPubkeyG1, bn254util.DeserializeG1(dasigners.SerializeG1(signer.PkG1)))
}
return dasigners.NewBN254G1Point(bn254util.SerializeG1(aggPubkeyG1)), big.NewInt(int64(len(quorum))), big.NewInt(int64(hit)), nil
}
func (d *DASignersPrecompile) GetAggPkG1(evm *EVM, method *abi.Method, args []interface{}) ([]byte, error) {
if len(args) != 3 {
return nil, ErrExecutionReverted
}
epochNumber := args[0].(*big.Int).Uint64()
quorumId := args[1].(*big.Int).Uint64()
quorumBitmap := args[2].([]byte)
aggPkG1, total, hit, err := d.getAggPkG1(evm, epochNumber, quorumId, quorumBitmap)
if err != nil {
return nil, err
}
return method.Outputs.Pack(aggPkG1, total, hit)
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,14 @@
package dasigners
import "errors"
var (
ErrQuorumIdOutOfBound = errors.New("quorum id out of bound")
ErrEpochOutOfBound = errors.New("epoch out of bound")
ErrRowIdOfBound = errors.New("row id out of bound")
ErrQuorumBitmapLengthMismatch = errors.New("quorum bitmap length mismatch")
ErrSignerNotFound = errors.New("signer not found")
ErrInvalidSender = errors.New("invalid sender")
ErrSignerExists = errors.New("signer exists")
ErrInvalidSignature = errors.New("invalid signature")
)

View file

@ -0,0 +1,63 @@
package dasigners
import (
"math/big"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/bn254util"
"github.com/ethereum/go-ethereum/crypto"
)
func PubkeyRegistrationHash(operatorAddress common.Address, chainId *big.Int) *bn254.G1Affine {
toHash := make([]byte, 0)
toHash = append(toHash, operatorAddress.Bytes()...)
// make sure chainId is 32 bytes
toHash = append(toHash, common.LeftPadBytes(chainId.Bytes(), 32)...)
toHash = append(toHash, []byte("0G_BN254_Pubkey_Registration")...)
msgHash := crypto.Keccak256(toHash)
// convert to [32]byte
var msgHash32 [32]byte
copy(msgHash32[:], msgHash)
// hash to G1
return bn254util.MapToCurve(msgHash32)
}
func EpochRegistrationHash(operatorAddress common.Address, epoch uint64, chainId *big.Int) *bn254.G1Affine {
toHash := make([]byte, 0)
toHash = append(toHash, operatorAddress.Bytes()...)
toHash = append(toHash, common.Uint64ToBytes(epoch)...)
toHash = append(toHash, common.LeftPadBytes(chainId.Bytes(), 32)...)
msgHash := crypto.Keccak256(toHash)
// convert to [32]byte
var msgHash32 [32]byte
copy(msgHash32[:], msgHash)
// hash to G1
return bn254util.MapToCurve(msgHash32)
}
func ValidateSignature(s IDASignersSignerDetail, hash *bn254.G1Affine, signature *bn254.G1Affine) bool {
pubkeyG1 := bn254util.DeserializeG1(SerializeG1(s.PkG1))
pubkeyG2 := bn254util.DeserializeG2(SerializeG2(s.PkG2))
gamma := bn254util.Gamma(hash, signature, pubkeyG1, pubkeyG2)
// pairing
P := [2]bn254.G1Affine{
*new(bn254.G1Affine).Add(signature, new(bn254.G1Affine).ScalarMultiplication(pubkeyG1, gamma)),
*new(bn254.G1Affine).Add(hash, new(bn254.G1Affine).ScalarMultiplication(bn254util.GetG1Generator(), gamma)),
}
Q := [2]bn254.G2Affine{
*new(bn254.G2Affine).Neg(bn254util.GetG2Generator()),
*pubkeyG2,
}
ok, err := bn254.PairingCheck(P[:], Q[:])
if err != nil {
return false
}
return ok
}

View file

@ -0,0 +1,87 @@
package dasigners
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func NewBN254G1Point(b []byte) BN254G1Point {
return BN254G1Point{
X: new(big.Int).SetBytes(b[:32]),
Y: new(big.Int).SetBytes(b[32:64]),
}
}
func SerializeG1(p BN254G1Point) []byte {
b := make([]byte, 0)
b = append(b, common.LeftPadBytes(p.X.Bytes(), 32)...)
b = append(b, common.LeftPadBytes(p.Y.Bytes(), 32)...)
return b
}
func NewBN254G2Point(b []byte) BN254G2Point {
return BN254G2Point{
X: [2]*big.Int{
new(big.Int).SetBytes(b[:32]),
new(big.Int).SetBytes(b[32:64]),
},
Y: [2]*big.Int{
new(big.Int).SetBytes(b[64:96]),
new(big.Int).SetBytes(b[96:128]),
},
}
}
func SerializeG2(p BN254G2Point) []byte {
b := make([]byte, 0)
b = append(b, common.LeftPadBytes(p.X[0].Bytes(), 32)...)
b = append(b, common.LeftPadBytes(p.X[1].Bytes(), 32)...)
b = append(b, common.LeftPadBytes(p.Y[0].Bytes(), 32)...)
b = append(b, common.LeftPadBytes(p.Y[1].Bytes(), 32)...)
return b
}
var (
signerKey = []byte{0x00} // signer => registered signer info
quorumKey = []byte{0x01} // epoch => quorumId => quorum
registrationKey = []byte{0x02} // signer => signature hash
quorumCountKey = []byte{0x03} // epoch => quorum count
epochNumberKey = []byte{0x04} // epoch number
epochBlockKey = []byte{0x05} // epoch number => block number
epochRegistrationKey = []byte{0x06} // epoch number => registration count
epochRegisteredSignerKey = []byte{0x07} // epoch number => index => signer
)
func SignerKey(account common.Address) common.Hash {
return crypto.Keccak256Hash(append(quorumCountKey, account.Bytes()...))
}
func QuorumKey(epochNumber uint64, quorumId uint64) common.Hash {
return crypto.Keccak256Hash(append(append(quorumKey, common.Uint64ToBytes(epochNumber)...), common.Uint64ToBytes(quorumId)...))
}
func RegistrationKey(epochNumber uint64, account common.Address) common.Hash {
return crypto.Keccak256Hash(append(append(registrationKey, common.Uint64ToBytes(epochNumber)...), account.Bytes()...))
}
func QuorumCountKey(epochNumber uint64) common.Hash {
return crypto.Keccak256Hash(append(quorumCountKey, common.Uint64ToBytes(epochNumber)...))
}
func EpochNumberKey() common.Hash {
return crypto.Keccak256Hash(epochNumberKey)
}
func EpochBlockKey(epochNumber uint64) common.Hash {
return crypto.Keccak256Hash(append(epochBlockKey, common.Uint64ToBytes(epochNumber)...))
}
func EpochRegistrationKey(epochNumber uint64) common.Hash {
return crypto.Keccak256Hash(append(epochRegistrationKey, common.Uint64ToBytes(epochNumber)...))
}
func EpochRegisteredSignerKey(epochNumber uint64, index uint64) common.Hash {
return crypto.Keccak256Hash(append(append(epochRegisteredSignerKey, common.Uint64ToBytes(epochNumber)...), common.Uint64ToBytes(index)...))
}

View file

@ -0,0 +1,7 @@
package precompiles
import "errors"
var (
ErrSenderNotOrigin = errors.New("sender not origin")
)

View file

@ -0,0 +1,18 @@
{
"extends": "solhint:recommended",
"plugins": ["prettier"],
"rules": {
"avoid-low-level-calls": "off",
"compiler-version": "off",
"gas-custom-errors": "off",
"explicit-types": ["warn", "implicit"],
"func-visibility": ["warn", { "ignoreConstructors": true }],
"max-states-count": "off",
"no-empty-blocks": "off",
"no-global-import": "off",
"no-inline-assembly": "off",
"not-rely-on-time": "off",
"prettier/prettier": "error",
"reason-string": "off"
}
}

View file

@ -0,0 +1,475 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"indexed": false,
"internalType": "struct BN254.G1Point",
"name": "pkG1",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256[2]",
"name": "X",
"type": "uint256[2]"
},
{
"internalType": "uint256[2]",
"name": "Y",
"type": "uint256[2]"
}
],
"indexed": false,
"internalType": "struct BN254.G2Point",
"name": "pkG2",
"type": "tuple"
}
],
"name": "NewSigner",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "socket",
"type": "string"
}
],
"name": "SocketUpdated",
"type": "event"
},
{
"inputs": [],
"name": "epochNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_quorumId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_quorumBitmap",
"type": "bytes"
}
],
"name": "getAggPkG1",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "aggPkG1",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "total",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "hit",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_quorumId",
"type": "uint256"
}
],
"name": "getQuorum",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_quorumId",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "_rowIndex",
"type": "uint32"
}
],
"name": "getQuorumRow",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "_account",
"type": "address[]"
}
],
"name": "getSigner",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "string",
"name": "socket",
"type": "string"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "pkG1",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256[2]",
"name": "X",
"type": "uint256[2]"
},
{
"internalType": "uint256[2]",
"name": "Y",
"type": "uint256[2]"
}
],
"internalType": "struct BN254.G2Point",
"name": "pkG2",
"type": "tuple"
}
],
"internalType": "struct IDASigners.SignerDetail[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_account",
"type": "address"
}
],
"name": "isSigner",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "makeEpoch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "params",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "tokensPerVote",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxVotesPerSigner",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxQuorums",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "epochBlocks",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "encodedSlices",
"type": "uint256"
}
],
"internalType": "struct IDASigners.Params",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
}
],
"name": "quorumCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "_signature",
"type": "tuple"
}
],
"name": "registerNextEpoch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "string",
"name": "socket",
"type": "string"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "pkG1",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256[2]",
"name": "X",
"type": "uint256[2]"
},
{
"internalType": "uint256[2]",
"name": "Y",
"type": "uint256[2]"
}
],
"internalType": "struct BN254.G2Point",
"name": "pkG2",
"type": "tuple"
}
],
"internalType": "struct IDASigners.SignerDetail",
"name": "_signer",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "_signature",
"type": "tuple"
}
],
"name": "registerSigner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_account",
"type": "address"
},
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
}
],
"name": "registeredEpoch",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_socket",
"type": "string"
}
],
"name": "updateSocket",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View file

@ -0,0 +1,87 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "minter",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getWA0GI",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "minter",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "minter",
"type": "address"
}
],
"name": "minterSupply",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "cap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "initialSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
}
],
"internalType": "struct Supply",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/377589efa765dec208356377872d3755.json"
}

View file

@ -0,0 +1,10 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "BN254",
"sourceName": "contracts/IDASigners.sol",
"abi": [],
"bytecode": "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220abc43d74fec9de74f3b7a60ee2e0337c23369163d8fb7a1e1cb0037a16d8abfb64736f6c63430008140033",
"deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220abc43d74fec9de74f3b7a60ee2e0337c23369163d8fb7a1e1cb0037a16d8abfb64736f6c63430008140033",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -0,0 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/377589efa765dec208356377872d3755.json"
}

View file

@ -0,0 +1,484 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IDASigners",
"sourceName": "contracts/IDASigners.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"indexed": false,
"internalType": "struct BN254.G1Point",
"name": "pkG1",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256[2]",
"name": "X",
"type": "uint256[2]"
},
{
"internalType": "uint256[2]",
"name": "Y",
"type": "uint256[2]"
}
],
"indexed": false,
"internalType": "struct BN254.G2Point",
"name": "pkG2",
"type": "tuple"
}
],
"name": "NewSigner",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "socket",
"type": "string"
}
],
"name": "SocketUpdated",
"type": "event"
},
{
"inputs": [],
"name": "epochNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_quorumId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_quorumBitmap",
"type": "bytes"
}
],
"name": "getAggPkG1",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "aggPkG1",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "total",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "hit",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_quorumId",
"type": "uint256"
}
],
"name": "getQuorum",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_quorumId",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "_rowIndex",
"type": "uint32"
}
],
"name": "getQuorumRow",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "_account",
"type": "address[]"
}
],
"name": "getSigner",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "string",
"name": "socket",
"type": "string"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "pkG1",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256[2]",
"name": "X",
"type": "uint256[2]"
},
{
"internalType": "uint256[2]",
"name": "Y",
"type": "uint256[2]"
}
],
"internalType": "struct BN254.G2Point",
"name": "pkG2",
"type": "tuple"
}
],
"internalType": "struct IDASigners.SignerDetail[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_account",
"type": "address"
}
],
"name": "isSigner",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "makeEpoch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "params",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "tokensPerVote",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxVotesPerSigner",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxQuorums",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "epochBlocks",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "encodedSlices",
"type": "uint256"
}
],
"internalType": "struct IDASigners.Params",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
}
],
"name": "quorumCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "_signature",
"type": "tuple"
}
],
"name": "registerNextEpoch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "string",
"name": "socket",
"type": "string"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "pkG1",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256[2]",
"name": "X",
"type": "uint256[2]"
},
{
"internalType": "uint256[2]",
"name": "Y",
"type": "uint256[2]"
}
],
"internalType": "struct BN254.G2Point",
"name": "pkG2",
"type": "tuple"
}
],
"internalType": "struct IDASigners.SignerDetail",
"name": "_signer",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint256",
"name": "X",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "Y",
"type": "uint256"
}
],
"internalType": "struct BN254.G1Point",
"name": "_signature",
"type": "tuple"
}
],
"name": "registerSigner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_account",
"type": "address"
},
{
"internalType": "uint256",
"name": "_epoch",
"type": "uint256"
}
],
"name": "registeredEpoch",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_socket",
"type": "string"
}
],
"name": "updateSocket",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -0,0 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/c79ff14473359160d0c7a65892d5cb52.json"
}

View file

@ -0,0 +1,96 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IWrappedA0GIBase",
"sourceName": "contracts/IWrappedA0GIBase.sol",
"abi": [
{
"inputs": [
{
"internalType": "address",
"name": "minter",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getWA0GI",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "minter",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "minter",
"type": "address"
}
],
"name": "minterSupply",
"outputs": [
{
"components": [
{
"internalType": "uint256",
"name": "cap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "initialSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
}
],
"internalType": "struct Supply",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -0,0 +1,78 @@
{
"_format": "hh-sol-cache-2",
"files": {
"/Users/wangfan/Project/0g-geth/core/vm/precompiles/interfaces/contracts/IDASigners.sol": {
"lastModificationDate": 1743021984706,
"contentHash": "dbe903452c6de71caa950fdb306027b3",
"sourceName": "contracts/IDASigners.sol",
"solcConfig": {
"version": "0.8.20",
"settings": {
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
">=0.8.0"
],
"artifacts": [
"BN254",
"IDASigners"
]
},
"/Users/wangfan/Project/0g-geth/core/vm/precompiles/interfaces/contracts/IWrappedA0GIBase.sol": {
"lastModificationDate": 1743006728499,
"contentHash": "1d44f536ff2a527d2afe8a4ef85adc7b",
"sourceName": "contracts/IWrappedA0GIBase.sol",
"solcConfig": {
"version": "0.8.20",
"settings": {
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
">=0.8.0"
],
"artifacts": [
"IWrappedA0GIBase"
]
}
}
}

View file

@ -0,0 +1,90 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0;
library BN254 {
struct G1Point {
uint X;
uint Y;
}
// Encoding of field elements is: X[1] * i + X[0]
struct G2Point {
uint[2] X;
uint[2] Y;
}
}
interface IDASigners {
/*=== struct ===*/
struct SignerDetail {
address signer;
string socket;
BN254.G1Point pkG1;
BN254.G2Point pkG2;
}
struct Params {
uint tokensPerVote;
uint maxVotesPerSigner;
uint maxQuorums;
uint epochBlocks;
uint encodedSlices;
}
/*=== event ===*/
event NewSigner(
address indexed signer,
BN254.G1Point pkG1,
BN254.G2Point pkG2
);
event SocketUpdated(address indexed signer, string socket);
/*=== function ===*/
function params() external view returns (Params memory);
function epochNumber() external view returns (uint);
function quorumCount(uint _epoch) external view returns (uint);
function isSigner(address _account) external view returns (bool);
function getSigner(
address[] memory _account
) external view returns (SignerDetail[] memory);
function getQuorum(
uint _epoch,
uint _quorumId
) external view returns (address[] memory);
function getQuorumRow(
uint _epoch,
uint _quorumId,
uint32 _rowIndex
) external view returns (address);
function registerSigner(
SignerDetail memory _signer,
BN254.G1Point memory _signature
) external;
function updateSocket(string memory _socket) external;
function registeredEpoch(
address _account,
uint _epoch
) external view returns (bool);
function registerNextEpoch(BN254.G1Point memory _signature) external;
function makeEpoch() external;
function getAggPkG1(
uint _epoch,
uint _quorumId,
bytes memory _quorumBitmap
)
external
view
returns (BN254.G1Point memory aggPkG1, uint total, uint hit);
}

View file

@ -0,0 +1,57 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0;
struct Supply {
uint256 cap;
uint256 initialSupply;
uint256 supply;
}
/**
* @title WrappedA0GIBase is a precompile for wrapped a0gi(wA0GI), it enables wA0GI mint/burn native 0g token directly.
*/
interface IWrappedA0GIBase {
/**
* @dev set the wA0GI address.
* It is designed to be called by governance module only so it's not implemented at EVM precompile side.
* @param addr address of wA0GI
*/
// function setWA0GI(address addr) external;
/**
* @dev get the wA0GI address.
*/
function getWA0GI() external view returns (address);
/**
* @dev set the cap and initial supply for a minter.
* It is designed to be called by governance module only so it's not implemented at EVM precompile side.
* @param minter minter address
* @param cap mint cap
* @param initialSupply initial mint supply
*/
// function setMinterCap(address minter, uint256 cap, uint256 initialSupply) external;
/**
* @dev get the mint supply of given address
* @param minter minter address
*/
function minterSupply(address minter) external view returns (Supply memory);
/**
* @dev mint a0gi to this precompile, add corresponding amount to minter's mint supply.
* If sender's final mint supply exceeds its mint cap, the transaction will revert.
* Can only be called by WA0GI.
* @param minter minter address
* @param amount amount to mint
*/
function mint(address minter, uint256 amount) external;
/**
* @dev burn given amount of a0gi on behalf of minter, reduce corresponding amount from sender's mint supply.
* Can only be called by WA0GI.
* @param minter minter address
* @param amount amount to burn
*/
function burn(address minter, uint256 amount) external;
}

View file

@ -0,0 +1,33 @@
import "hardhat-abi-exporter";
import { HardhatUserConfig } from "hardhat/types";
const config: HardhatUserConfig = {
paths: {
artifacts: "build/artifacts",
cache: "build/cache",
sources: "contracts",
},
solidity: {
compilers: [
{
version: "0.8.20",
settings: {
evmVersion: "istanbul",
optimizer: {
enabled: true,
runs: 200,
},
},
},
],
},
abiExporter: {
path: "./abis",
runOnCompile: true,
clear: true,
flat: true,
format: "json",
},
};
export default config;

View file

@ -0,0 +1,27 @@
{
"name": "precompile-contracts",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"build": "hardhat compile",
"fmt:sol": "prettier 'contracts/**/*.sol' -w"
},
"devDependencies": {
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"eslint": "8.34.0",
"eslint-config-prettier": "8.6.0",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-prettier": "4.2.1",
"hardhat": "^2.22.2",
"hardhat-abi-exporter": "^2.10.1",
"prettier": "2.8.4",
"prettier-plugin-organize-imports": "3.2.4",
"prettier-plugin-solidity": "1.1.2",
"solhint": "^4.5.4",
"solhint-plugin-prettier": "0.0.5",
"ts-node": "^10.9.2",
"typescript": "4.9.5"
}
}

File diff suppressed because it is too large Load diff

46
core/vm/statedb_utils.go Normal file
View file

@ -0,0 +1,46 @@
package vm
import (
"bytes"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// StoreBytes stores an arbitrary-length []byte into StateDB using multiple storage slots.
func StoreBytes(statedb StateDB, addr common.Address, key common.Hash, value []byte) {
// Compute the storage root key (keccak256(key)) as the base
lengthKey := crypto.Keccak256Hash(key.Bytes())
statedb.SetState(addr, lengthKey, common.BigToHash(big.NewInt(int64(len(value)))))
// Split value into 32-byte chunks and store them
for i := 0; i < len(value); i += 32 {
end := i + 32
if end > len(value) {
end = len(value)
}
chunk := make([]byte, 32)
copy(chunk, value[i:end]) // Copy to ensure 32 bytes, padding with zeros
// Compute storage key: keccak256(lengthKey || index)
storageKey := crypto.Keccak256Hash(append(lengthKey.Bytes(), common.Uint64ToBytes(uint64(i/32))...))
statedb.SetState(addr, storageKey, common.BytesToHash(chunk))
}
}
// LoadBytes retrieves an arbitrary-length []byte from StateDB.
func LoadBytes(statedb StateDB, addr common.Address, key common.Hash) []byte {
// Read length
lengthKey := crypto.Keccak256Hash(key.Bytes())
length := statedb.GetState(addr, lengthKey).Big().Uint64()
// Read stored chunks
var buffer bytes.Buffer
for i := uint64(0); i < length; i += 32 {
storageKey := crypto.Keccak256Hash(append(lengthKey.Bytes(), common.Uint64ToBytes(uint64(i/32))...))
chunk := statedb.GetState(addr, storageKey).Bytes()
buffer.Write(chunk)
}
return buffer.Bytes()[:length] // Trim padding
}

2
go.mod
View file

@ -140,6 +140,8 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/mod v0.22.0 // indirect

4
go.sum
View file

@ -514,6 +514,10 @@ github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

View file

@ -76,7 +76,7 @@ func fuzz(id byte, data []byte) int {
} }
cpy := make([]byte, len(data)) cpy := make([]byte, len(data))
copy(cpy, data) copy(cpy, data)
_, err := precompile.Run(cpy) _, err := precompile.Run(nil, &vm.Contract{Input: cpy}, true)
if !bytes.Equal(cpy, data) { if !bytes.Equal(cpy, data) {
panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy)) panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy))
} }