mirror of
https://github.com/luxfi/lamport.git
synced 2026-10-30 16:21:45 +00:00
230 lines
6.4 KiB
Go
230 lines
6.4 KiB
Go
// Package precompile provides the EVM precompile interface for Lamport signatures.
|
|
//
|
|
// The Lamport precompile enables gas-efficient on-chain verification for
|
|
// Lux-native chains. For remote chains, use the pure Solidity verifier.
|
|
//
|
|
// Precompile Address: 0x0200000000000000000000000000000000000006
|
|
//
|
|
// Input format (ABI-encoded):
|
|
// - message: bytes32 (32 bytes)
|
|
// - signature: bytes[256] (256 * 32 = 8192 bytes)
|
|
// - publicKey: bytes32[2][256] (256 * 2 * 32 = 16384 bytes)
|
|
//
|
|
// Output: bool (32 bytes, ABI-encoded)
|
|
//
|
|
// Gas cost: 3000 base + 50 per hash check = ~15,800 gas
|
|
// (vs ~100,000+ gas for pure Solidity verification)
|
|
package precompile
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
|
|
"github.com/luxfi/lamport/primitives"
|
|
)
|
|
|
|
const (
|
|
// PrecompileAddress is the address of the Lamport precompile
|
|
PrecompileAddress = "0x0200000000000000000000000000000000000006"
|
|
|
|
// GasBase is the base gas cost
|
|
GasBase = 3000
|
|
|
|
// GasPerHash is the gas cost per keccak256 hash verification
|
|
GasPerHash = 50
|
|
|
|
// TotalGas is the total gas for verification (256 hashes)
|
|
TotalGas = GasBase + (primitives.KeyBits * GasPerHash) // 15,800
|
|
|
|
// InputSizeMessage is the size of the message input (32 bytes)
|
|
InputSizeMessage = 32
|
|
|
|
// InputSizeSignature is the size of the signature input
|
|
InputSizeSignature = primitives.SignatureSize // 8192
|
|
|
|
// InputSizePublicKey is the size of the public key input
|
|
InputSizePublicKey = primitives.PublicKeySize // 16384
|
|
|
|
// MinInputSize is the minimum valid input size
|
|
MinInputSize = InputSizeMessage + InputSizeSignature + InputSizePublicKey // 24608
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidInput indicates the input format is invalid
|
|
ErrInvalidInput = errors.New("lamport precompile: invalid input")
|
|
|
|
// ErrOutOfGas indicates insufficient gas for verification
|
|
ErrOutOfGas = errors.New("lamport precompile: out of gas")
|
|
)
|
|
|
|
// PrecompileContract implements the Lamport verification precompile.
|
|
type PrecompileContract struct{}
|
|
|
|
// RequiredGas returns the gas required for the input.
|
|
func (c *PrecompileContract) RequiredGas(input []byte) uint64 {
|
|
if len(input) < MinInputSize {
|
|
return 0 // Invalid input, will fail in Run
|
|
}
|
|
return TotalGas
|
|
}
|
|
|
|
// Run executes the Lamport verification precompile.
|
|
//
|
|
// Input format:
|
|
//
|
|
// [0:32] - message (bytes32)
|
|
// [32:8224] - signature (bytes[256], each element is 32 bytes)
|
|
// [8224:24608] - publicKey (bytes32[2][256])
|
|
//
|
|
// Returns:
|
|
// - 32 bytes: ABI-encoded bool (1 = valid, 0 = invalid)
|
|
func (c *PrecompileContract) Run(input []byte) ([]byte, error) {
|
|
if len(input) < MinInputSize {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
|
|
// Parse message (bytes32)
|
|
var message [32]byte
|
|
copy(message[:], input[0:32])
|
|
|
|
// Parse signature (bytes[256])
|
|
var sig primitives.Signature
|
|
for i := 0; i < primitives.KeyBits; i++ {
|
|
offset := 32 + (i * 32)
|
|
copy(sig.Preimages[i][:], input[offset:offset+32])
|
|
}
|
|
|
|
// Parse public key (bytes32[2][256])
|
|
var pub primitives.PublicKey
|
|
pubOffset := 32 + primitives.SignatureSize
|
|
for i := 0; i < primitives.KeyBits; i++ {
|
|
offset0 := pubOffset + (i * 64)
|
|
offset1 := offset0 + 32
|
|
copy(pub.Hashes[i][0][:], input[offset0:offset0+32])
|
|
copy(pub.Hashes[i][1][:], input[offset1:offset1+32])
|
|
}
|
|
|
|
// Verify signature
|
|
valid := primitives.Verify(&pub, message, &sig)
|
|
|
|
// Return ABI-encoded bool
|
|
result := make([]byte, 32)
|
|
if valid {
|
|
result[31] = 1
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// EncodeInput encodes the verification inputs for the precompile.
|
|
func EncodeInput(message [32]byte, sig *primitives.Signature, pub *primitives.PublicKey) []byte {
|
|
input := make([]byte, MinInputSize)
|
|
|
|
// Encode message
|
|
copy(input[0:32], message[:])
|
|
|
|
// Encode signature
|
|
for i := 0; i < primitives.KeyBits; i++ {
|
|
copy(input[32+(i*32):32+(i*32)+32], sig.Preimages[i][:])
|
|
}
|
|
|
|
// Encode public key
|
|
pubOffset := 32 + primitives.SignatureSize
|
|
for i := 0; i < primitives.KeyBits; i++ {
|
|
copy(input[pubOffset+(i*64):pubOffset+(i*64)+32], pub.Hashes[i][0][:])
|
|
copy(input[pubOffset+(i*64)+32:pubOffset+(i*64)+64], pub.Hashes[i][1][:])
|
|
}
|
|
|
|
return input
|
|
}
|
|
|
|
// DecodeOutput decodes the precompile output to a boolean.
|
|
func DecodeOutput(output []byte) bool {
|
|
if len(output) < 32 {
|
|
return false
|
|
}
|
|
return output[31] == 1
|
|
}
|
|
|
|
// ABIEncodedSignature converts a signature to ABI-encoded format.
|
|
// This is useful for generating calldata for Solidity contracts.
|
|
func ABIEncodedSignature(sig *primitives.Signature) []byte {
|
|
// ABI-encoded dynamic array: offset (32) + length (32) + data (256 * 32)
|
|
// For simplicity, we just encode the raw preimages
|
|
return sig.Bytes()
|
|
}
|
|
|
|
// ABIEncodedPublicKey converts a public key to ABI-encoded format.
|
|
func ABIEncodedPublicKey(pub *primitives.PublicKey) []byte {
|
|
return pub.Bytes()
|
|
}
|
|
|
|
// PrecompileAddressBytes returns the precompile address as bytes.
|
|
func PrecompileAddressBytes() [20]byte {
|
|
var addr [20]byte
|
|
addr[1] = 0x02 // 0x0200...006
|
|
addr[19] = 0x06
|
|
return addr
|
|
}
|
|
|
|
// GasEstimate returns the estimated gas for verification.
|
|
func GasEstimate() uint64 {
|
|
return TotalGas
|
|
}
|
|
|
|
// InputBuilder helps construct precompile input.
|
|
type InputBuilder struct {
|
|
data []byte
|
|
}
|
|
|
|
// NewInputBuilder creates a new input builder.
|
|
func NewInputBuilder() *InputBuilder {
|
|
return &InputBuilder{
|
|
data: make([]byte, 0, MinInputSize),
|
|
}
|
|
}
|
|
|
|
// SetMessage sets the message (bytes32).
|
|
func (b *InputBuilder) SetMessage(message [32]byte) *InputBuilder {
|
|
if len(b.data) < 32 {
|
|
b.data = append(b.data, make([]byte, 32-len(b.data))...)
|
|
}
|
|
copy(b.data[0:32], message[:])
|
|
return b
|
|
}
|
|
|
|
// SetSignature sets the signature.
|
|
func (b *InputBuilder) SetSignature(sig *primitives.Signature) *InputBuilder {
|
|
if len(b.data) < 32+primitives.SignatureSize {
|
|
b.data = append(b.data, make([]byte, 32+primitives.SignatureSize-len(b.data))...)
|
|
}
|
|
for i := 0; i < primitives.KeyBits; i++ {
|
|
copy(b.data[32+(i*32):], sig.Preimages[i][:])
|
|
}
|
|
return b
|
|
}
|
|
|
|
// SetPublicKey sets the public key.
|
|
func (b *InputBuilder) SetPublicKey(pub *primitives.PublicKey) *InputBuilder {
|
|
if len(b.data) < MinInputSize {
|
|
b.data = append(b.data, make([]byte, MinInputSize-len(b.data))...)
|
|
}
|
|
offset := 32 + primitives.SignatureSize
|
|
for i := 0; i < primitives.KeyBits; i++ {
|
|
copy(b.data[offset+(i*64):], pub.Hashes[i][0][:])
|
|
copy(b.data[offset+(i*64)+32:], pub.Hashes[i][1][:])
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Build returns the constructed input.
|
|
func (b *InputBuilder) Build() []byte {
|
|
return b.data
|
|
}
|
|
|
|
// Uint256 helper for Solidity compatibility
|
|
func uint256ToBytes(n uint64) []byte {
|
|
result := make([]byte, 32)
|
|
binary.BigEndian.PutUint64(result[24:32], n)
|
|
return result
|
|
}
|