chain-signer

Suíte de segurança de pré-assinatura para agentes de IA: sinaliza drenagens de carteira, phishing de permissão e ações arriscadas antes de assinar em cadeias EVM. Sem custódia.

Documentação

chain-signer

PyPI Python License Release

Um conjunto de segurança para agentes de IA — o cinto de segurança que pega a coisa perigosa ANTES que aconteça. Três guardiões, cada um acionável por conta própria (e como ferramentas MCP), emparelhando com qualquer carteira ou pilha de identidade:

  • preflight(tx) — decodificar uma transação não assinada e sinalizar drenagens antes de assinar (aprovação ilimitada/grande, aprovar-tudo, token e NFT transferFrom, atualização de proxy, permissão on-chain, Permit2 on-chain approve/permit/transferFrom, aprovações escondidas em multicall incl. lotes do roteador Uniswap e Multicall3 aggregate/aggregate3/aggregate3Value (o auxiliar de lote em cada cadeia EVM), aprovações envolvidas em ERC-4337/smart-account execute/executeBatch, Gnosis Safe multiSend/execTransaction e DSProxy execute, drenagens roteadas através do Uniswap Universal Router (comandos Permit2 permit/transferFrom incl. sub-planos), 1inch AggregationRouter v5 swap() com saída redirecionada ou slippage zero, 0x ExchangeProxy transformERC20() com slippage zero, delegação de conta EIP-7702, will-revert).
  • inspect_typed_data(td) — pegar phishing de permissão em uma mensagem EIP-712 antes que o agente a assine (ERC-2612, Uniswap Permit2 incl. SignatureTransfer + variantes de testemunha, e permissões estilo DAI) e ordens Seaport que dão ativos de graça — consideração zero, lucros roteados para um terceiro, ou escondidos em uma árvore BulkOrder.
  • check_action(action, policy) — aplicar limites de permitir/proibir + valor/destinatário antes que o agente aja.

Todos os três são à prova de falhas e são guardiões, não garantias. Também incluído: uma carteira multi-chain não custodial (burner, saldo, enviar, trocar) — o agente detém sua própria chave e assina localmente. Sem MetaMask, sem conta, sem custódia.

from chain_signer import assert_safe
assert_safe(tx)   # raises if the tx is a drain/unlimited-approval/revert — review before signing

Instalação

pip install chain-signer
export ETHERSCAN_API_KEY=...   # for live balance reads + broadcast (Etherscan v2)

O suporte a Bitcoin/Solana é opcional: pip install "chain-signer[all]".

Início rápido (10 segundos — offline, sem chave, sem fundos, sem rede)

pip install chain-signer
from chain_signer import preflight
spender = "0x" + "22" * 20
tx = {"to": "0x" + "33" * 20, "data": "0x095ea7b3" + spender[2:].rjust(64, "0") + "f" * 64, "value": 0}
print(preflight(tx))   # ok=False — flags unlimited_approval before you'd ever sign

Esse é o ponto: a drenagem é sinalizada antes que você a assine — sem chave, sem fundos, sem rede.

Carteira incluída (opcional — os guardiões emparelham com qualquer carteira)

from chain_signer import burner, send_ether
from chain_signer.balance import get_balance

w = burner()                          # fresh throwaway wallet; the agent owns w.private_key
print(w.address, get_balance(w))      # live on-chain balance
send_ether(w, "0x...recipient", 0.001)  # auto nonce+gas, signed locally, broadcast

Demos completos executáveis estão no repositório: examples/agent_safety_demo.py (os três guardiões param três ataques reais) e examples/quickstart.py (carteira) — clone para executá-los, ou apenas importe como acima.

Pré-verificação de segurança (o ponto)

Antes de um agente assinar, entregue a tx não assinada a preflight() — ela decodifica o calldata e retorna os riscos, ou use assert_safe() para parar completamente em um sinalizador ALTO. Offline, sem rede, nunca levanta.

from chain_signer import preflight, assert_safe

# an unlimited-allowance approve() to a spender — the classic drain setup
tx = {"to": token, "data": "0x095ea7b3" + spender_padded + "f"*64, "value": 0}

report = preflight(tx)
# {'decoded': {...}, 'ok': False,
#  'risk_flags': [{'code': 'unlimited_approval', 'severity': 'HIGH',
#                  'detail': 'approve() grants an effectively-unlimited allowance ...'}]}

assert_safe(tx)          # raises ValueError on a HIGH flag; pass force=True to override
assert_safe(tx, sim=my_simulator)   # optional: also flag will-revert via your simulation hook

O que ela sinaliza hoje: aprovação ilimitada/grande, increaseAllowance, setApprovalForAll, ERC-20 transferFrom + ERC-721/1155 safeTransferFrom (drenagens de token e NFT), ERC-777 authorizeOperator/operatorSend (drenagens de concessão de operador + puxada de operador), ERC-2612 on-chain e estilo DAI permit, Permit2 on-chain approve/permit/transferFrom (único e lote — o roteador de aprovação dominante: permissão uint160 ilimitada + puxada de drenagem) além de Permit2 SignatureTransfer permit(Witness)TransferFrom (a puxada de permissão assinada de uma vez que protocolos de intenção/preenchimento usam), proxy upgradeTo/upgradeToAndCall, aprovações escondidas dentro de multicall (todas as variantes de roteador, aninhadas) e Multicall3 aggregate/aggregate3/aggregate3Value (o auxiliar de lote canônico implantado em um endereço em cada cadeia EVM), aprovações envolvidas em ERC-4337/smart-account execute/executeBatch, Gnosis Safe multiSend/execTransaction, ou DSProxy execute(target,data)/execute(code,data) (decodificado e recursado), drenagens roteadas através do Uniswap Universal Router (execute(commands,inputs) — comandos Permit2 permit/transferFrom, lote e EXECUTE_SUB_PLAN), delegação de conta EIP-7702 (o drenador de "atualização de carteira"), valor nativo grande, calldata opaco, chamadas malformadas, e will-revert (com um hook de simulação). Limites honestos (leia isto): esta é uma análise ESTÁTICA — ela decodifica calldata e corresponde a padrões de drenagem conhecidos. NÃO é um simulador de transações: não pegará uma drenagem nova/obfuscada que não consegue decodificar (essas recebem um sinalizador de baixa gravidade "desconhecido", não um bloqueio), e scanners baseados em simulação vão mais fundo aí. A cobertura de segurança é apenas EVM hoje (sem análise de tx Solana/Bitcoin). E ainda não é comprovado em campo em escala. Um guardião de primeira linha para padrões conhecidos — não uma garantia. Combine com simulação + revisão humana para ações de alto valor.

Inspetor de mensagens assinadas (a metade off-chain)

Uma drenagem não precisa de uma transação. Um dApp pode pedir ao agente para assinar uma mensagem EIP-712 — mais perigosamente um permit concedendo uma permissão ilimitada de token, que preflight (uma verificação de tx) não consegue ver. inspect_typed_data() pega isso antes que o agente assine:

from chain_signer import inspect_typed_data
report = inspect_typed_data(typed_data)   # the EIP-712 object you're about to sign
# ok=False, risk_flags=[{'code': 'unlimited_permit_signature', 'severity': 'HIGH', ...}]

Cobre as três principais formas de permissão: ERC-2612, Uniswap Permit2 (PermitSingle/PermitBatch, além de SignatureTransfer e as variantes de testemunha que os protocolos de intenção usam), e estilo DAI (allowed: true), além de ordens de marketplace Seaport que entregam ativos de graça — consideração zero, lucros roteados para um terceiro enquanto seu ativo sai, ou a mesma doação enterrada em uma árvore merkle BulkOrder. Offline, nunca levanta.

Assinante protegido (verificação + assinatura em uma chamada)

inspect_typed_data só protege quando o agente lembra de chamá-lo primeiro — sign_typed_data sozinho assinará feliz uma mensagem de phishing de permissão. guarded_sign_typed_data() compõe os dois para que a assinatura seja verificada por padrão: ele inspeciona e depois se recusa a assinar uma drenagem de ALTO risco.

from chain_signer import guarded_sign_typed_data, SignatureBlocked
sig = guarded_sign_typed_data(wallet, domain, types, message, "Permit")  # raises SignatureBlocked on a drain

Em uma mensagem limpa, a assinatura é byte-idêntica a sign_typed_data; passe force=True para substituir.

Portão de política de ação (inspecione o que o agente FAZ)

A identidade diz quem é o agente; ela não impede uma ação ruim. check_action() aplica uma política em uma chamada de ferramenta proposta antes de executá-la — à prova de falhas (nega em entrada ilegível):

from chain_signer import check_action
policy = {"forbid_tools": ["bridge"], "max_value_wei": 10**18, "allow_recipients": [trusted_addr]}
r = check_action({"tool": "send", "args": {"to": addr, "value_wei": 5*10**18}}, policy)
# {'allowed': False, 'violations': [{'code': 'value_over_limit', ...}]}

Todos os três guardiões são expostos como ferramentas MCP (preflight, inspect_signature, check_action) — qualquer runtime de agente (Claude, Cursor, …) pode chamá-los diretamente, somente leitura, sem chave.

O que é pego e o que não é — o mapa honesto de cobertura de ameaças: docs/THREAT-COVERAGE.md.

O que você obtém

  • preflight(tx) / assert_safe(tx) — decodificar uma tx não assinada e sinalizar padrões de drenagem antes de assinar.
  • inspect_typed_data(td) — sinalizar phishing de permissão em uma mensagem EIP-712 antes que o agente a assine.
  • guarded_sign_typed_data(w, domain, types, message, primary_type) — verificar e depois assinar; recusa uma drenagem.
  • check_action(action, policy) — aplicar limites de permitir/proibir + valor/destinatário antes que o agente aja.
  • burner() — uma carteira nova para uma tarefa única; descarte-a quando terminar.
  • restore(key) — recarregar uma carteira depois a partir de sua chave privada exportada (mesma chave → mesmo endereço).
  • send_ether(w, to, amount) — enviar em ETH (não wei); nonce, gás e transmissão tratados para você.
  • get_balance(w) — saldo ao vivo da cadeia (indexador Etherscan v2, não um RPC público instável).
  • swap(...) — trocas de token via 0x/Paraswap.
  • Carteiras opcionais Solana + Bitcoin via o extra [all].

Garantia não custodial

A chave privada é gerada/carregada localmente, usada apenas para assinar, e nunca é registrada, retornada ou armazenada por esta biblioteca. Você detém a chave; nunca tocamos em seus fundos. Esse é o design inteiro.

Lidando com a chave (leia isto)

w.private_key é a chave da carteira. Trate-a como uma senha:

  • NUNCA a registre, imprima em produção ou escreva em notas/memória/chat. Quem a tiver controla os fundos.
  • Para uma carteira descartável com alguns dólares, isso é de baixo risco por design — mas a regra ainda vale.
  • Para reutilizar uma carteira depois, armazene a chave em um gerenciador de segredos / variável de ambiente, então restore(key).
  • Melhor: export_encrypted(w, password) fornece um dicionário de keystore protegido por senha para armazenar em repouso; load_encrypted(keystore, password) traz a carteira de volta. Nunca armazene a chave bruta se puder armazenar o keystore.

Idiom de assinatura (nota para usuários de web3.py)

A carteira não expõe métodos sign_transaction / sign_message. A assinatura é feita por funções auxiliares para as quais você passa a carteira — por exemplo, send_ether(w, to, amount) assina e transmite, e sign_message(w, "text") retorna uma assinatura EIP-191 para fluxos de autenticação / login (recuperável via eth_account Account.recover_message).

CLI no PATH

pip install pode avisar que o diretório de script chain-signer não está no seu PATH. A biblioteca funciona independentemente; para usar a CLI diretamente, adicione esse diretório ao PATH ou execute python -m chain_signer ....

Superfície de ferramentas (para qualquer IA / MCP / CLI)

chain_signer.mcp_server expõe list_tools() e call_tool(name, arguments). CLI:

python -m chain_signer list
python -m chain_signer call create_wallet '{"chain":"evm"}'

Uso responsável

Ferramenta de propósito geral, não custodial. Você é responsável por usá-la dentro das leis e termos de serviço que se aplicam a você. Não é destinada ou comercializada para qualquer negociação restrita ou proibida em sua jurisdição.

Notas

  • Saldos/transmissão usam o indexador Etherscan v2 (autoritativo), nunca um RPC público gratuito.
  • Blocos de construção de baixo nível (tx.send, call_contract, nonce/gás explícitos) permanecem disponíveis para uso avançado.

Pague uma API x402 em uma chamada

from chain_signer import burner, sign_x402_payment
w = burner()
payload = sign_x402_payment(w, token=USDC, to=PAY_TO, value=1000, valid_before=EXPIRES, chain_id=8453)
# -> {"signature", "authorization"} ready for the x402 payment header. Signed locally, no prompt.

Constrói + assina a autorização EIP-3009 que o x402 espera (o esquema "exato"). Seu agente paga uma API paga por conta própria — sem prompt de senha, sem cadastro, sem custódia.

Assinar dados tipados (EIP-712) — para pagamentos de agente / x402

from chain_signer import burner, sign_typed_data
w = burner()
sig = sign_typed_data(w, domain, types, message)  # EIP-712; for x402 / EIP-3009 authorizations

Seu agente pode autorizar um pagamento assinando dados tipados localmente — sem prompt de senha, sem cadastro.

Execute como um servidor MCP

chain-signer também é um servidor Model Context Protocol (MCP), então agentes cientes de MCP podem usá-lo diretamente:

pip install chain-signer
chain-signer-mcp          # speaks MCP over stdio (JSON-RPC 2.0)

Expõe 9 ferramentas. Os três guardiões de segurança (o ponto): preflight, inspect_signature, check_action. Além da carteira não custodial: create_wallet, get_balance, send, call_contract, swap, bridge.

Conecte-o a qualquer cliente MCP (Claude Desktop, Cursor, etc.) adicionando-o à configuração mcpServers do cliente:

{
  "mcpServers": {
    "chain-signer": {
      "command": "chain-signer-mcp",
      "env": { "ETHERSCAN_API_KEY": "your-key-for-live-balance-and-broadcast" }
    }
  }
}

Isso é tudo — o agente agora pode verificar cada tx, assinatura e ação através dos guardiões antes de agir, e (opcionalmente) manter sua própria carteira para ler saldos, enviar e trocar como ferramentas nativas. (ETHERSCAN_API_KEY é opcional; necessário apenas para leituras de saldo ao vivo e transmissão.)