Carteira
Chaves, endereços e envios — mais o contrato de comportamento que toda carteira neste protocolo tem de cumprir. Cada regra descreve uma forma de perder dinheiro que o protocolo permite, e que uma carteira precisa portanto impedir.
Os comandos#
zcd wallet new --out KEYFILE # create an encrypted key file
zcd wallet address --key KEYFILE # show an address
zcd wallet balance --key KEYFILE # ask a node for a balance
zcd wallet send --key KEYFILE --to ADDR --amount N
zcd wallet sweep --key KEYFILE --to ADDR --one-shot
zcd wallet retire --key KEYFILE [--addr ADDR]
zcd ui --key KEYFILE # the same wallet in a browser
A carteira gráfica não é uma segunda implementação. zcd wallet, zcd
ui e a aplicação desktop são três interfaces sobre uma única
wallet/session: cada uma constrói seu certificado ali, e as verificações de regra rodam
dentro dessa chamada. Uma carteira gráfica é portanto estruturalmente incapaz de ser mais permissiva
que a CLI — não porque quem a escreveu foi cuidadoso, mas porque não existe um segundo caminho
de código no qual ela pudesse ser.
Dois tipos de endereço#
| Versão | Tipo | Use para | Reutilizável? |
|---|---|---|---|
0x01 | Uso único | Um único pagamento esperado. Derive um novo por pagamento recebido. | Não. Um débito o queima permanentemente. |
0x02 | Persistente | Comerciantes, endereços de doação, carteiras quentes, pagamentos de mineração. | Sim, para sempre. Nunca pode entrar no registro de gastos. |
Uso único versus persistente é uma política de carteira exposta como um bit de endereço, não dois
livros-razão. As carteiras assumem 0x01 por pagamento recebido, porque é isso que torna um
pagamento não vinculável ao seguinte; quem quiser uma conta usa 0x02.
As oito regras#
Cada regra abaixo descreve uma forma de perder dinheiro ou taxas que o protocolo permite — deliberadamente, porque a alternativa era pior — e que uma carteira precisa portanto evitar. A carteira de referência as implementa. Ela não apenas as documenta.
Regra 1 — varra células inteiras#
Ao gastar a partir de um endereço 0x01, mova o saldo inteiro
— para o beneficiário e para um endereço de troco que você controle. O certificado carrega um
MARK_SPENT desse endereço, e depois que ele se aplica toda leitura e escrita sob o
endereço falha permanentemente. Não há uma segunda transação.
A cadeia agora suaviza isso: o que quer que um endereço queimado ainda detenha vai para a própria célula
RefundTo do certificado no commit — para o saldo nativo do endereço e para cada
célula que o próprio certificado nomeie. Então varrer de menos não destrói mais os restos; ele os entrega ao
seu endereço de troco.
Um certificado tem um único RefundTo e pode queimar endereços de uso único pertencentes a
vários assinantes, então um resíduo sob a sua célula pode ser entregue à deles.
Nunca coassine um certificado que queime um endereço de uso único seu e reembolse para um
endereço que você não controla.
O que esta regra não pode salvar: um saldo em um ativo que o certificado nunca menciona. Os ativos sob um endereço não são deriváveis de um slot, então alcançar um ativo não nomeado significaria varrer toda a tabela de células no único estágio que não paraleliza. Nomeie cada ativo no certificado que queima o endereço.
Regra 2 — o RefundTo precisa ser um endereço que você ainda possa usar#
Ele precisa nomear ou um endereço persistente que você controle ou um endereço de uso único
novo que este certificado não queime. Assentar em uma célula queimada deixa o
restante encalhado: o fold o queima em vez de escrevê-lo em uma célula que ninguém pode ler, e o reporta como
refund_burned em vez de refunded, para que uma carteira reconciliando um saldo possa
perceber.
Regra 3 — um endereço, um pagamento esperado#
Duas obrigações, uma de cada lado:
- Recebendo. Derive um endereço
0x01novo por pagamento que você espera. Nunca publique um duas vezes. Não varra nem aposente um endereço que você divulgou nos últimosttl_maxblocos, a menos que aceite que um pagamento em trânsito para ele será omitido e cobrará seu remetente. - Enviando. Recuse pagar um endereço de uso único que você possa ver que já foi creditado ou gasto. Se um beneficiário lhe entregar um endereço uma segunda vez, trate isso como erro, não como conveniência.
Para qualquer coisa paga mais de uma vez — um comerciante, um endereço de doação, um pagamento
de mineração — use um endereço persistente (0x02). Esta é a única
regra que remove a exposição em vez de estreitá-la.
Regra 4 — cadeias dependentes: confirme, ou aceite o risco#
Incremente o Seq para cada certificado que dependa de um anterior. Dentro de um
bloco o fold compromete os certificados de um assinante na ordem de Seq, então uma cadeia dependente no
mesmo bloco é segura. Entre blocos ela não é: transmita Seq = n+1
apenas depois que Seq = n tiver confirmado, ou aceite que o dependente pode se comprometer sozinho
e sofrer omissão. Isto não é um defeito do protocolo — o assinante aceitou o risco de obsolescência ao assinar.
Regra 5 — defina o máximo com generosidade e a prioridade com honestidade#
Cada mercado recebe dois preços. O máximo é um limite de solvência: assim que a taxa base o ultrapassa o certificado se torna não incluível e precisa ser assinado de novo. A prioridade é o que um minerador de fato recebe.
- Elevar o máximo não custa nada em taxas — a margem de segurança é gratuita.
- Mas o depósito reserva
gas × max, então o máximo limita quanto saldo fica travado para um passo do fold. Dimensione-o contra o saldo de fato disponível. - Um certificado com TTL longo precisa de mais folga que um com TTL curto, porque a taxa base tem mais blocos nos quais se mover.
A saída de emergência para saldos pequenos é encolher a janela, não a segurança: assine com um TTL curto e reassine no vencimento, trocando imobilização por latência. A resposta errada é manter o TTL longo e encolher o máximo, o que torna o certificado encalhável exatamente quando o mercado se move — o caso para o qual a folga existia.
Regra 6 — ordene os movimentos canonicamente#
O protocolo não impõe ordem alguma aos movimentos de uma transferência, então sem uma ordem canônica uma nova tentativa
do mesmo pagamento lógico produz um id diferente e a carteira não consegue distinguir "já enviado" de
"enviado duas vezes". O wallet.Transfer ordena por ativo, origem, destino, e então valor, de modo que uma
nova tentativa reproduz o id.
Uma nova tentativa não precisa reproduzir a assinatura: a preimagem do id exclui a lista de assinaturas, então uma nova tentativa reassinada com um nonce novo é o mesmo id e a rede a recusa como a duplicata que ela é. A idempotência é uma propriedade que o protocolo fornece, para toda carteira em vez de apenas para as cuidadosas.
Regra 7 — nunca entregue uma seed#
A seed é a chave. Qualquer um que a leia é dono de tudo o que ambos os seus endereços detêm — o de uso único e o persistente, que não têm relação na cadeia mas são derivados da mesma chave.
Os arquivos de chave são sempre criptografados: Argon2id sobre a frase secreta, AES-256-GCM sobre a seed, ambos com seus parâmetros armazenados no arquivo para que um usuário trancado para fora deste binário possa recuperar com a biblioteca padrão de qualquer linguagem. Não há flag para escrever um sem criptografia. As frases secretas são lidas do terminal sem eco, nunca de uma flag — uma frase secreta em uma linha de comando está no histórico do shell e na tabela de processos.
A escrita mantém duas propriedades ao mesmo tempo, e ambas são sobre a mesma frase — perder uma é perder dinheiro:
- Nunca sobrescrever silenciosamente. Um arquivo de chave já presente no destino é deixado intacto e a escrita falha.
- Nunca deixar um pela metade. O seed vai para um arquivo temporário no mesmo diretório, sofre fsync ali, e só então é publicado sob seu nome final numa única operação de sistema de arquivos.
Ambas valem em todo sistema de arquivos contra o qual a CLI foi medida, FAT32 e exFAT inclusos — os formatos que um backup USB de armazenamento frio tem mais chance de usar.
FAT32 e exFAT não têm bits de permissão Unix, então o 0600 com que um arquivo de chave é criado
não sobrevive neles — as opções de montagem decidem. No Linux o fmask padrão
tipicamente o deixa legível por todos; o macOS monta esses volumes como
noowners, o que reporta 0700 e não impõe nada. Escolha a frase secreta
de acordo. Eles também não têm journal, então se uma publicação for interrompida em FAT e reportar falha,
confira o destino antes de rodar de novo.
Regra 8 — recuse o que a rede vai recusar#
Uma carteira que reporta sucesso para algo que a rede só pode descartar moveu a falha para um lugar onde o usuário nunca vai olhar. Duas formas disso, ambas encontradas em campo:
- Bytes que nenhum par consegue decodificar. O codec é uma autoridade por direito próprio, com regras que o validador não repete. O construtor agora afirma a propriedade diretamente: o que quer que ele emita, o decodificador aceita.
- Uma transferência que o fold só pode omitir. Uma transferência acima do saldo da origem não quebra regra alguma, então todo nó a admite — e nada recusa um produtor que a inclua mesmo assim, e nesse ponto o fold a liquida ao
skip_fee, queimado do depósito e pago a ninguém. O caso ruim não é "sem efeito", é "a taxa foi queimada e o valor nunca se moveu".
O zcd wallet send --force contorna aquela única recusa e nada mais,
porque um depósito que se espera que chegue dentro da janela do TTL faz o mesmo certificado se aplicar e a
carteira não consegue ver o futuro. Ele não pode tornar válido um certificado inválido; só pode submeter um
válido que pode sofrer omissão — e uma omissão não é grátis.
Confiando em um nó que você não roda#
O zcd e a carteira não são nós completos. Eles acreditam no que um nó lhes diz, e uma
CLI que fala RPC com um nó que ela própria não valida precisa confiar em alguma coisa a respeito
das respostas desse nó — isso não é um bug a corrigir, é o que uma carteira é. Três mitigações,
cada uma para uma mentira específica:
| Mitigação | O que ela pega |
|---|---|
--devnet / --testnet / --params | O operador afirma para qual rede pretende assinar, e todo nó consultado é conferido contra essa afirmação. A identidade da rede nunca é tomada de um nó sozinho. Isso vale também para balance, não só para os comandos que assinam. |
--confirm-rpc NODE | Nomeia um segundo nó independente. O saldo e a marca de gasto de todo endereço precisam ser reportados de forma idêntica pelos dois antes que algo prossiga. Um nó cujo chain_id discorde é recusado, e um --confirm-rpc que nomeie o mesmo endpoint que --rpc já nomeia é recusado de saída — um nó que confere a si mesmo concorda consigo mesmo. |
| A confirmação digitada | Antes de submeter, o zcd wallet sweep imprime os números exatos e exige que você digite sweep. --yes pula apenas esse prompt, nunca as verificações acima dele. |
Checklist para uma implementação de carteira#
Se você está escrevendo uma carteira contra este protocolo, este é o contrato:
- Gastar de
0x01move o saldo inteiro, contando a reserva do depósito como parte dele — inclusive em programas sem movimento algum. RETIRErecusa um alvo que ainda detenha saldo.- O relato de saldo do nó não é tratado como inquestionável: identidade de rede afirmada pelo operador, uma segunda fonte conferível, e os números exatos confirmados antes de uma varredura irreversível.
RefundToé validado como persistente ou novo antes da assinatura.- Receber deriva um endereço novo por pagamento esperado; enviar recusa um endereço de uso único já creditado ou gasto.
- Endereços de comerciante e de pagamento assumem
0x02por padrão. Seqincrementa por certificado dependente, e a carteira espera a confirmação antes de transmitir o próximo — ou diz em voz alta que não está esperando.- Os máximos de taxa são dimensionados a partir do saldo disponível e do TTL, não fixados no código.
- Os movimentos são ordenados canonicamente, para que uma repetição reproduza o id do certificado.
- Os arquivos de chave são criptografados, escritos de forma durável e sem sobrescrita silenciosa, e as frases secretas nunca chegam a uma flag.
- Nada é assinado se a própria codificação não puder ser decodificada de volta, e nada é submetido que o saldo de origem não possa cobrir — com qualquer contorno nomeado, estreito e reportado na prévia.