ZYCORD 문서
한국어
Zycord문서지갑

지갑

키, 주소, 전송 — 그리고 이 프로토콜 위의 모든 지갑이 충족해야 하는 동작 계약. 각 규칙은 프로토콜이 허용하는, 따라서 지갑이 막아야 하는 돈을 잃는 방식 하나씩을 설명합니다.

명령들#

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

그래픽 지갑은 두 번째 구현이 아닙니다. zcd wallet, zcd ui, 그리고 데스크톱 애플리케이션은 하나의 wallet/session 위에 놓인 세 개의 인터페이스입니다. 각각은 거기서 자기 인증서(certificate)를 만들고, 규칙 검사는 그 호출 안에서 실행됩니다. 따라서 그래픽 지갑은 CLI보다 더 관대할 수 있는 구조적 가능성 자체가 없습니다 — 그것을 쓴 사람이 조심스러웠기 때문이 아니라, 더 관대할 수 있는 두 번째 코드 경로가 아예 없기 때문입니다.

두 종류의 주소#

버전종류어디에 쓰는가재사용 가능한가?
0x01일회성예상되는 결제 한 건. 받는 결제마다 새로 유도하세요.아니요. 차변이 발생하면 영구히 소각됩니다.
0x02영구상점, 기부 주소, 핫 월렛, 채굴 지급.예, 영원히. 사용된 주소 레지스트리에 결코 들어갈 수 없습니다.

일회성이냐 영구냐는 두 개의 원장이 아니라 주소 비트로 드러난 지갑 정책입니다. 지갑은 받는 결제마다 0x01을 기본으로 씁니다. 그것이 어떤 결제를 다음 결제와 연결할 수 없게 만들어 주기 때문입니다. 계정을 원하는 사람은 0x02를 씁니다.

여덟 가지 규칙#

아래의 모든 규칙은 프로토콜이 허용하는 — 대안이 더 나빴기 때문에 의도적으로 허용하는 — 돈이나 수수료를 잃는 방식 하나씩을 설명하며, 따라서 지갑이 막아야 하는 것들입니다. 참조 지갑은 이 규칙들을 구현합니다. 문서로만 적어 두지 않습니다.

규칙 1 — 셀(cell)을 통째로 쓸어 담기#

0x01 주소에서 지출할 때는 잔액 전체를 옮기세요 — 수취인에게로, 그리고 여러분이 통제하는 잔돈 주소로 말입니다. 그 인증서(certificate)는 해당 주소의 MARK_SPENT를 담고 있으며, 그것이 적용되고 나면 그 주소 아래의 모든 읽기와 쓰기가 영구히 실패합니다. 두 번째 트랜잭션은 없습니다.

이제 체인이 이 점을 다소 누그러뜨립니다. 소각된 주소가 여전히 갖고 있는 것은 커밋 시점에 그 인증서(certificate) 자신의 RefundTo 셀(cell)로 옮겨집니다 — 주소의 네이티브 잔액에 대해서도, 인증서가 직접 지목한 모든 셀에 대해서도 그렇습니다. 그러니 덜 쓸어 담는 것이 더 이상 남은 것을 파괴하지 않고, 여러분의 잔돈 주소로 전달합니다.

여기에 따라오는 의무

인증서(certificate)에는 RefundTo가 하나뿐이고 여러 서명자에게 속한 일회성 주소들을 소각할 수 있으므로, 여러분의 셀(cell) 아래에 남은 것이 다른 사람의 주소로 전달될 수 있습니다. 여러분의 일회성 주소를 소각하면서 여러분이 통제하지 않는 주소로 환불하는 인증서에는 결코 함께 서명하지 마세요.

이 규칙이 구해 주지 못하는 것: 인증서(certificate)가 전혀 언급하지 않은 자산에 담긴 잔액입니다. 어떤 주소 아래에 어떤 자산이 있는지는 슬롯에서 유도할 수 없으므로, 지목되지 않은 자산에 손을 뻗으려면 병렬화되지 않는 바로 그 단계에서 셀(cell) 테이블 전체를 훑어야 합니다. 그 주소를 소각하는 인증서 안에 모든 자산을 지목하세요.

규칙 2 — RefundTo는 여전히 쓸 수 있는 주소여야 한다#

여러분이 통제하는 영구 주소이거나, 이 인증서(certificate)가 소각하지 않는 새로운 일회성 주소를 지목해야 합니다. 소각된 셀(cell)로 정산하면 남은 것이 오도 가도 못하게 됩니다. 폴드(fold)는 아무도 읽을 수 없는 셀에 그것을 써 넣는 대신 소각하고, refunded가 아니라 refund_burned로 보고하므로 잔액을 대조하는 지갑이 이를 알아챌 수 있습니다.

규칙 3 — 하나의 주소에 하나의 예상 결제#

의무가 둘 있으며, 양쪽에 하나씩입니다.

  • 받기. 예상하는 결제마다 새 0x01 주소를 유도하세요. 같은 주소를 두 번 공개하지 마세요. 최근 ttl_max 블록 이내에 공개한 주소는, 그 주소로 오고 있는 결제가 건너뜀(skip) 처리되어 발신자에게 청구될 수 있음을 받아들이는 것이 아니라면 쓸어 담거나 폐쇄하지 마세요.
  • 보내기. 이미 입금되었거나 지출된 것이 보이는 일회성 주소로는 지불하기를 거부하세요. 수취인이 같은 주소를 두 번째로 건네준다면, 그것을 편의가 아니라 오류로 다루세요.

두 번 이상 지불받는 모든 것 — 상점, 기부 주소, 채굴 지급 — 에는 영구 주소(0x02)를 쓰세요. 이것이 노출을 좁히는 데 그치지 않고 아예 없애는 유일한 규칙입니다.

규칙 4 — 의존 사슬은 확정을 기다리거나 위험을 받아들이기#

이전 인증서(certificate)에 의존하는 모든 인증서마다 Seq를 증가시키세요. 하나의 블록 안에서 폴드(fold)는 한 서명자의 인증서들을 Seq 순서로 커밋하므로, 같은 블록 안의 의존 사슬은 안전합니다. 블록을 넘어가면 안전하지 않습니다. Seq = n이 확정된 뒤에만 Seq = n+1을 전파하거나, 의존하는 쪽이 혼자 커밋되어 건너뜀(skip) 처리될 수 있음을 받아들이세요. 이는 프로토콜의 결함이 아닙니다 — 서명자는 서명함으로써 낡음의 위험을 받아들인 것입니다.

규칙 5 — 최대값은 넉넉하게, 우선 수수료는 정직하게#

각 시장은 두 개의 가격을 받습니다. 최대값은 지급 능력의 한계입니다. 기본 수수료가 그 값을 넘어서면 인증서(certificate)는 포함될 수 없게 되어 다시 서명해야 합니다. 우선 수수료는 채굴자가 실제로 받는 몫입니다.

  • 최대값을 올리는 데는 수수료가 전혀 들지 않습니다 — 안전 여유분은 공짜입니다.
  • 다만 예치금이 gas × max를 예약하므로, 최대값은 폴드(fold) 한 단계를 위해 얼마나 많은 잔액이 묶이는지를 결정합니다. 실제로 쓸 수 있는 잔액에 맞춰 크기를 정하세요.
  • TTL이 긴 인증서(certificate)는 짧은 것보다 더 넉넉한 여유가 필요합니다. 기본 수수료가 움직일 블록이 더 많기 때문입니다.

잔액이 적을 때의 탈출구는 안전 여유가 아니라 기간을 줄이는 것입니다. 짧은 TTL로 서명하고 만료되면 다시 서명해서, 묶임을 지연 시간과 맞바꾸세요. 잘못된 대응은 긴 TTL을 유지한 채 최대값을 줄이는 것인데, 그러면 시장이 움직이는 바로 그때 인증서가 오도 가도 못하게 됩니다 — 여유분이 존재하는 이유가 바로 그 상황입니다.

규칙 6 — 이동을 정규 순서로 정렬하기#

프로토콜은 전송에 담긴 이동들에 어떤 순서도 강제하지 않으므로, 정규 순서가 없으면 논리적으로 같은 결제를 다시 시도했을 때 다른 id가 나오고 지갑은 "이미 보냄"과 "두 번 보냄"을 구분할 수 없습니다. wallet.Transfer는 자산, 출발지, 목적지, 그다음 금액 순으로 정렬하므로 재시도가 같은 id를 재현합니다.

재시도가 서명까지 재현할 필요는 없습니다. id의 원상은 서명 목록을 제외하므로, 새 nonce로 다시 서명한 재시도도 같은 id이고 네트워크는 그것을 있는 그대로 중복으로 보고 거부합니다. 멱등성은 프로토콜이 제공하는 성질이며, 조심스러운 지갑만이 아니라 모든 지갑에 제공됩니다.

규칙 7 — 시드를 결코 건네주지 않기#

시드가 곧 키입니다. 그것을 읽은 사람은 그 두 주소가 가진 모든 것을 소유합니다 — 일회성 주소와 영구 주소는 체인 위에서는 서로 무관하지만 같은 키에서 유도됩니다.

키 파일은 언제나 암호화됩니다. 암호구에는 Argon2id를, 시드에는 AES-256-GCM을 쓰며, 둘 다 파라미터가 파일 안에 저장되므로 이 바이너리를 쓸 수 없게 된 사용자도 어떤 언어의 표준 라이브러리로든 복구할 수 있습니다. 암호화하지 않고 기록하는 플래그는 없습니다. 암호구는 화면에 표시되지 않는 방식으로 터미널에서 읽으며, 플래그로는 결코 받지 않습니다 — 명령줄에 적힌 암호구는 셸 히스토리와 프로세스 목록에 남습니다.

기록은 두 가지 성질을 동시에 지키며, 둘 다 같은 문장에 관한 것입니다 — 하나를 잃는 것은 곧 돈을 잃는 것입니다.

  • 소리 없이 덮어쓰지 말 것. 목적지에 이미 키 파일이 있으면 그것을 건드리지 않고 기록이 실패합니다.
  • 찢어진 파일을 남기지 말 것. 시드는 같은 디렉터리의 임시 파일에 기록되고 거기서 fsync된 뒤, 단 한 번의 파일 시스템 연산으로 최종 이름 아래에 공개됩니다.

이 둘은 CLI가 측정된 모든 파일 시스템에서 지켜지며, FAT32와 exFAT도 포함됩니다 — 콜드 스토리지용 USB 백업이 가장 흔히 쓰는 형식들입니다.

FAT로 포맷된 USB에 담긴 암호화된 키 파일을 지키는 것은 그 암호구뿐이며, 그 밖에는 아무것도 없습니다

FAT32와 exFAT에는 Unix 권한 비트가 없으므로, 키 파일이 만들어질 때 붙는 0600은 거기서 살아남지 못합니다 — 마운트 옵션이 결정합니다. Linux에서 기본 fmask는 보통 파일을 모두가 읽을 수 있게 남겨 두고, macOS는 그런 볼륨을 noowners로 마운트하는데, 이는 0700이라고 보고하면서 아무것도 강제하지 않습니다. 그에 맞춰 암호구를 고르세요. 이들에는 저널도 없으므로, FAT에서 공개 과정이 중단되고 실패가 보고되면 다시 실행하기 전에 목적지를 확인하세요.

규칙 8 — 네트워크가 거부할 것은 미리 거부하기#

네트워크가 버릴 수밖에 없는 것에 대해 성공을 보고하는 지갑은 사용자가 결코 들여다보지 않을 곳으로 실패를 옮겨 놓은 것입니다. 이런 형태가 둘 있으며, 둘 다 실제로 발견된 것입니다.

  • 어떤 피어도 디코딩할 수 없는 바이트. 코덱은 그 자체로 하나의 권위이며, 검증기가 다시 진술하지 않는 규칙들을 갖고 있습니다. 이제 빌더는 그 성질을 직접 단언합니다. 자신이 내놓는 것은 무엇이든 디코더가 받아들인다는 것입니다.
  • 폴드(fold)가 건너뜀(skip) 처리할 수밖에 없는 전송. 출발지 잔액을 넘는 전송은 어떤 규칙도 어기지 않으므로 모든 노드가 이를 수용합니다 — 그리고 그럼에도 그것을 포함시키는 생산자를 거부하는 것은 아무것도 없습니다. 그 시점에 폴드는 그것을 skip_fee로 정산하는데, 이 수수료는 예치금에서 소각되어 아무에게도 지급되지 않습니다. 나쁜 경우는 "아무 효과 없음"이 아니라 "수수료는 소각되었는데 가치는 움직이지 않음"입니다.

zcd wallet send --force는 그 하나의 거부만을 우회하고 다른 것은 우회하지 않습니다. TTL 창 안에 도착할 것으로 예상되는 예치금이 같은 인증서(certificate)를 적용되게 만들 수 있는데 지갑은 미래를 볼 수 없기 때문입니다. 이 플래그가 유효하지 않은 인증서를 유효하게 만들 수는 없고, 건너뜀(skip) 처리될 수 있는 유효한 인증서를 제출할 수 있게 할 뿐입니다 — 그리고 건너뜀은 공짜가 아닙니다.

직접 운영하지 않는 노드를 신뢰한다는 것#

zcd와 지갑은 풀 노드가 아닙니다. 이들은 노드가 말해 주는 것을 믿으며, 자기가 직접 검증하지 않는 노드와 RPC로 대화하는 CLI는 그 노드의 답변에 관해 무언가를 신뢰할 수밖에 없습니다 — 이는 고쳐야 할 버그가 아니라 지갑이라는 것의 본질입니다. 완화책이 셋 있으며, 각각 특정한 거짓말에 대응합니다.

완화책무엇을 잡아내는가
--devnet / --testnet / --params운영자가 어느 네트워크에 대해 서명하려는지 단언하고, 질의하는 모든 노드가 그 단언에 대조해 확인됩니다. 네트워크 신원은 결코 노드 하나에서만 가져오지 않습니다. 이는 서명하는 명령들뿐 아니라 balance에도 적용됩니다.
--confirm-rpc NODE독립적인 두 번째 노드를 지정합니다. 모든 주소의 잔액 과 사용됨 표시가 양쪽에서 동일하게 보고되어야만 다음 단계로 넘어갑니다. chain_id가 어긋나는 노드는 거부되며, --rpc가 이미 지목한 엔드포인트를 지목하는 --confirm-rpc는 즉시 거부됩니다 — 자기 자신을 교차 확인하는 노드는 자기 자신과 일치하기 마련입니다.
타이핑으로 하는 확인제출하기 전에 zcd wallet sweep은 정확한 숫자들을 출력하고 sweep이라고 직접 입력하도록 요구합니다. --yes는 이 확인 절차만 건너뛰며, 그 위의 검사들은 결코 건너뛰지 않습니다.

지갑 구현을 위한 점검 목록#

이 프로토콜 위에서 지갑을 만들고 있다면, 계약은 다음과 같습니다.

  • 0x01에서의 지출은 예치금 예약분까지 포함해 잔액 전체를 옮깁니다 — 이동이 전혀 없는 프로그램에서도 그렇습니다.
  • RETIRE는 아직 잔액이 남아 있는 대상을 거부합니다.
  • 노드가 보고하는 잔액을 의심의 여지 없는 것으로 다루지 않습니다. 네트워크 신원은 운영자가 단언하고, 두 번째 출처로 교차 확인할 수 있으며, 되돌릴 수 없는 쓸어 담기 전에 정확한 숫자를 확인합니다.
  • RefundTo는 서명 전에 영구 주소이거나 새 주소인지 검증됩니다.
  • 받기는 예상되는 결제마다 새 주소를 유도하고, 보내기는 이미 입금되었거나 지출된 일회성 주소를 거부합니다.
  • 상점 주소와 지급 주소는 기본값이 0x02입니다.
  • Seq는 의존하는 인증서(certificate)마다 증가하며, 지갑은 다음 것을 전파하기 전에 확정을 기다립니다 — 그러지 않는다면 그렇게 하지 않는다고 분명히 말합니다.
  • 수수료 최대값은 하드코딩되지 않고 사용 가능한 잔액과 TTL로부터 정해집니다.
  • 이동들은 정규 순서로 정렬되므로 재시도가 같은 인증서(certificate) id를 재현합니다.
  • 키 파일은 암호화되고, 소리 없는 덮어쓰기 없이 내구성 있게 기록되며, 암호구는 결코 플래그에 실리지 않습니다.
  • 자기 인코딩으로부터 다시 디코딩할 수 없는 것은 아무것도 서명하지 않고, 출발지 잔액으로 감당할 수 없는 것은 아무것도 제출하지 않습니다 — 모든 우회 수단은 이름이 붙어 있고, 범위가 좁으며, 미리보기에 보고됩니다.