이 번역은 참고용입니다. 정식 기준은 영어판이며, 내용이 다를 경우 영어판이 우선합니다. 영어판 읽기

서명

선택적인 레코드 수준 `sigs` 배열 — 레코드 본문 전체를 대상으로 하는 분리형 COSE_Sign1, 도메인 분리된 서명 대상 페이로드, 두 가지 서명자 키 운반 경로, 그리고 엄격한 Ed25519 검증을 다룹니다.

Label 309 레코드는 선택적인 최상위 sigs 배열에 하나 이상의 저작자 서명을 담을 수 있습니다(MAY). 각 항목은 레코드 본문을 대상으로 하는 분리형 COSE_Sign1(RFC 9052)이며, 어떤 키가 해당 레코드를 보증한다는 사실을 입증합니다. 저작자성은 언제나 선택 사항입니다 — 표준은 서명을 결코 요구하지 않으며, sigs 필드가 없는 레코드도 그 자체로 완전하며 온전히 검증 가능한 존재 증명입니다.

서명은 부가적입니다. 타임스탬프 주장을 대체하는 것이 아니라, 그 위에 "그리고 이 키가 그것을 보증한다"라는 사실을 덧붙여 답할 뿐입니다. 일차적인 주장은 콘텐츠 해시이며, 서명은 누가 그 주장을 뒷받침하는가에 관한 메타데이터에 불과합니다. 결정적으로, 검증자가 확인할 수 없는 서명 — 지원되지 않는 알고리즘이거나 해석할 수 없는 키 — 이라 하더라도 콘텐츠나 타임스탬프 주장을 무효화하는 일은 결코 없습니다. 서명은 부드럽게 실패하지만, 존재는 그렇지 않습니다.

이 페이지에서는 서명이 무엇을 대상으로 하는지, 서명되는 정확한 바이트 열, 서명자의 공개 키를 운반하는 두 가지 방식, 그리고 공개 검증자가 수행하는 엄격한 검증을 정의합니다. Ed25519 키 자체는 에서 정의하며, 와이어 상의 sigs 필드 — cose_sign1cose_key가 각각 단일 CBOR 바이트 문자열인 형태 — 는 레코드에서 정의합니다.

서명이 대상으로 하는 것

하나의 sigs[i] 항목은 레코드 본문 전체를 일률적으로 입증합니다. 아이템 단위, URI 단위, 필드 단위의 서명 단위는 존재하지 않습니다. 하나의 서명이 모든 아이템, 모든 스토리지 URI, 모든 암호화 봉투, 존재한다면 supersedes 포인터, 그리고 레코드가 담은 모든 확장 키에 커밋합니다. 릴레이는 사후에 이들 중 어느 하나라도 서명을 깨뜨리지 않고서는 추가하거나 삭제하거나 다시 쓸 수 없습니다.

서명 대상 본문은 sigs 필드를 제거한 레코드 맵, 즉 remove_keys(record_map, ["sigs"])이며, 여기서는 이를 record_body로 표기합니다. 서명은 자기 자신을 대상으로 할 수 없고, 또한 각 서명자는 공동 서명자의 명단이 아니라 주장에만 커밋하므로, 각 항목의 서명 대상에서 sigs 배열은 제외됩니다. 구체적으로, 모든 항목은 {v, items?, merkle?, supersedes?, crit?, <extensions?>}에 서명합니다. 이는 모든 항목에 대해 동일한 record_body 바이트 열이지만, 어떤 항목도 sigs 안의 다른 항목에는 서명하지 않습니다. 따라서 서명자는 자신이 서명한 본문이 다른 모든 항목이 묶여 있는 본문과 동일하다는 사실을 입증하는 것이며, 어느 다른 서명자가 공동 서명했는지를 입증하는 것은 아닙니다.

서명의 범위는 레코드 본문이지 트랜잭션이 아닙니다

검증된 서명은 어떤 키가 레코드 본문을 대상으로 서명을 생성했음을 증명합니다. 같은 키가 그것을 운반하는 트랜잭션을 제출했다거나, 그 수수료를 지불했다거나, 그 블록 시각을 선택했다는 것을 증명하지는 않습니다. 동일한 레코드 본문은 이후의 트랜잭션에서 누구든 다시 게시할 수 있으며(MAY), 이는 의도된 레코드 이식성입니다. 검증된 서명은 "<key>가 서명함"으로 표시하고, 결코 "<key>가 이것을 제출함"이나 "<time>에 <key>가 게시함"으로 표시하지 마십시오.

서명 대상 페이로드

각 항목은 분리형 COSE_Sign1을 담으므로 COSE의 페이로드 필드는 비어 있으며, 실제로 서명되는 바이트 열은 온체인 레코드로부터 검증자가 재구성합니다. 서명자는 다음과 같이 계산합니다.

record_body       = remove_keys(record_map, ["sigs"])
record_body_bytes = canonical_cbor(record_body)
SIG_DOMAIN_RECORD = utf8("cardano-poe-record-sig-v1")        ; 25 bytes
to_sign           = SIG_DOMAIN_RECORD || record_body_bytes   ; concatenation
Sig_structure     = [ "Signature1", protected, h'', to_sign ]
signature         = Sign(canonical_cbor(Sig_structure), signer_key)

record_bodyRFC 8949 §4.2.1에 따라 정준 CBOR로 직렬화됩니다 — 레코드 전체가 사용하는 것과 동일한 결정론적 인코딩입니다. 서명이 상호 운용될 수 있는 것은 바로 이 결정론 덕분입니다. 동일한 논리적 본문을 인코딩하는 두 구현은 바이트 단위로 동일한 record_body_bytes를 생성하므로, 한쪽이 생성한 서명을 다른 쪽에서도 검증할 수 있습니다.

도메인 분리 접두사

to_sign은 25바이트 UTF-8 문자열 cardano-poe-record-sig-v1record_body_bytes의 앞에 붙인 것입니다. 이 접두사는 서명을 그 Label 309 상의 역할에 묶고 프로토콜 간 재전송을 방지합니다. 설령 본문과 동일한 CBOR 구조(같은 키, 같은 타입)를 우연히 공유하는 미래의 Cardano 메타데이터 스키마가 있다 하더라도, 그것에 대해 Label 309 서명을 재사용할 수는 없습니다. 그 스키마의 to_sign은 다른 접두사를 가지거나 접두사가 아예 없으므로 서명되는 바이트 열이 달라지고 서명은 실패합니다. 구현은 이 리터럴 바이트 열을 to_sign의 선두 바이트로 정확히 그대로 삽입해야 합니다(MUST). 접두사 없이 정준 CBOR만 서명하는 것은 규격에 부합하지 않습니다.

external_aad가 비어 있는 이유

Label 309는 도메인 분리자를 COSE의 external_aad가 아니라 to_sign내부에 둡니다. external_aad 슬롯(Sig_structure[2])은 항상 빈 바이트 문자열 h''입니다. 이는 도메인 문자열을 external_aad에 넣는 통상적인 COSE 패턴으로부터의 의도적인 이탈이며, 그 이유는 지갑 상호 운용성에 있습니다. CIP-30 signData — Cardano에서 표준이 되는 지갑 서명 경로 — 는 external_aad를 일절 사용하지 않는다고 규정하며, dApp이 그것을 공급할 수단을 제공하지 않습니다. 비어 있지 않은 external_aad는 지갑이 생성한 모든 서명을 실패하게 만들 것입니다. 접두사를 페이로드에 삽입함으로써, 동일한 재전송 방지 특성을 보존하는 동시에 지갑이 생성한 바이트 열과 검증자가 재계산한 바이트 열을 바이트 단위로 동일하게 유지합니다.

Sig_structure

Sig_structureRFC 9052 §4.4에서 정의하는 4요소 COSE_Sign1 서명용 배열입니다.

슬롯비고
[0]"Signature1"고정된 COSE 컨텍스트 식별자로, UTF-8 바이트 그대로가 아니라 완전한 CBOR 텍스트 문자열(11바이트)로 출력됩니다.
[1]protected서명자의 bstr로 감싼 정준 CBOR 보호 헤더 바이트 열로, 그대로 사용되며 검증자가 다시 정준화하는 일은 없습니다.
[2]external_aad항상 h''(길이 0의 bstr).
[3]to_sign25바이트 접두사를 record_body_bytes에 연결한 것.

게시되는 COSE_Sign1은 그 페이로드 필드(COSE_Sign1[2])를 CBOR null(0xF6)로 담습니다 — 즉 분리형 형태입니다. 길이 0의 바이트 문자열을 포함하여, 첨부된 페이로드는 거부됩니다. 페이로드를 분리하는 것이야말로 서명되는 바이트 열을 검증자가 독립적으로 재계산하는 레코드 본문에 고정하는 방법입니다. 첨부형 형태라면 생성자가 온체인 주장과 아무런 관계도 없는 빌려온 바이트 열에 서명할 수 있게 됩니다.

하드웨어 지갑의 해시 모드

CIP-30 / CIP-8은 리소스가 제약된 하드웨어 공동 서명자가 설정할 수 있는 선택적 비보호 헤더 플래그 "hashed": true를 정의합니다. 이것이 존재하고 참인 경우, Sig_structure[3]to_sign 자체가 아니라 28바이트의 Blake2b-224(to_sign) 다이제스트가 되며, 나머지 세 슬롯은 변하지 않습니다. 검증자는 엄격한 Ed25519 검증에 앞서 비보호 헤더를 검사하고 이 치환을 수행해야 합니다(MUST). 소프트웨어 및 SDK 생성자는 이것을 설정하지 않는 것이 좋습니다(SHOULD NOT) — 와이어 상의 바이트를 절약하지 못하면서 검증자의 코드 경로만 복잡하게 만들기 때문입니다.

서명 알고리즘

v1의 유일한 서명 알고리즘은 EdDSA over Ed25519 (RFC 8032)이며, COSE_Sign1 보호 헤더에 존재하는 COSE alg = -8(RFC 9053 §2.2)로 식별됩니다. v1 검증자의 필수 기준선은 {-8}이며, 검증자는 이에 더해 -19(Ed25519, 완전 지정형)를 받아들이고 두 코드포인트를 동일한 Ed25519 프리미티브 아래에서 검증할 수도 있습니다(MAY). 레지스트리는 확장 가능합니다 — 향후 개정에서는 포스트 양자 서명이 파괴적 변경이 아니라 부가적으로 추가됩니다.

서명자 키 해석

공개 검증자는 어떤 서비스에도 연결하지 않고 서명자의 공개 키를 해석할 수 있어야 하므로, 모든 서명은 그 키 자체, 또는 서명 내에 놓인 명확한 키 참조를 온체인으로 운반합니다. v1에는 운반 형태가 정확히 두 가지 있으며, 이들은 단일 항목 내에서 상호 배타적입니다 — 둘 다 사용하는 항목은 구조 오류입니다.

경로 1 — 아이덴티티 서명(서명 내 kid)

32바이트 원시 Ed25519 공개 키를 COSE_Sign1의 보호 헤더 내 COSE 헤더 레이블 4(kid, RFC 9052 §3.1)에 둡니다. 이 항목은 cose_key 필드를 담지 않습니다. Label 309 관례에 따라, 정확히 32바이트인 보호 헤더 kid는 그 자체가 공개 키이며, 대역 외에서 조회되는 키를 가리키는 불투명한 포인터가 아닙니다. 32바이트라는 길이는 명확한 판별자입니다. Ed25519 공개 키는 항상 32바이트이기 때문입니다. 키를 (비보호 헤더가 아니라) 보호 헤더에 두는 것은 그것을 서명에 묶는 일이며, 이를 다시 쓴 공격자는 검증을 깨뜨리게 됩니다.

이 관례는 RFC 9052에서 kid를 불투명한 식별자로 읽는 해석으로부터의 의도적이고 문서화된 이탈입니다. 이것이야말로 아이덴티티 경로를 키 디렉터리 없이도 서비스에 의존하지 않게 만드는 요소입니다. 키 모델은 에서 정의합니다.

경로 2 — 지갑 서명(인라인 cose_key)

CIP-30 signData 서명은 서명자의 공개 키를 COSE_Sign1 내부가 아니라 별도의 cbor<COSE_Key> 블롭으로 반환합니다. 그러한 서명을 레코드에 엮는 생성자는 그 COSE_Key를 단일 CBOR 바이트 문자열로서 동일한 sigs[i] 항목 내 키 cose_key 아래에 두어야 합니다(MUST). 검증자는 그것을 COSE_Key로 디코딩하고 레이블 -2에서 Ed25519 공개 키를 읽습니다. COSE_Key는 공개 키 절반만, 즉 kty = OKP (1), crv = Ed25519 (6), 레이블 -2의 32바이트 x만을 기술해야 하며(MUST), 개인 키 자료(레이블 -4 등)를 운반해서는 안 됩니다(MUST NOT). 영구 장부에 개인 스칼라를 게시하는 것은 되돌릴 수 없는 키 유출입니다.

상호 배타

두 경로는 와이어 수준에서 배타적입니다. 항목은 32바이트 보호 헤더 kid를 담고 cose_key담지 않거나 (경로 1), 또는 cose_key 필드를 담고 32바이트 보호 헤더 kid담지 않거나(경로 2) 둘 중 하나이며, 결코 둘 다 담지 않습니다. 둘 다 담은 항목은 거부되므로, 검증자는 검증 시점에 모호함을 해소할 필요가 없습니다. 따라서 해석은 우선순위가 매겨진 우열이 아니라 와이어 수준의 판별입니다.

경로조건서명자 키
132바이트 보호 kid, cose_key 없음32바이트 kid 값을 직접 사용.
2cose_key 존재, 32바이트 kid 없음COSE_Key 레이블 -2의 Ed25519 키.

비보호 헤더에만 운반되는 kid는 인정된 해석 경로가 아닙니다. 그것은 서명된 봉투의 바깥에 자리하므로, 릴레이가 서명을 깨뜨리지 않고 다시 쓸 수 있습니다. 검증자는 해석을 위해 비보호 헤더의 kid 값을 무시해야 합니다(MUST). 허용된 어떤 경로로도 32바이트 Ed25519 키를 얻지 못하면, 그 항목은 미해석으로 보고되며 어떤 저작자성 주장에도 기여하지 않습니다.

검증

공개 검증자는 각 sigs[i]를 독립적으로 다음 순서로 확인합니다.

  1. 디코드. sigs[i].cose_sign1 바이트 문자열을 COSE_Sign1로 파싱합니다. 페이로드 필드는 null(분리형)이어야 하며(MUST), null이 아니거나 비어 있지 않은 페이로드는 모두 잘못된 형식입니다.
  2. 알고리즘. 보호 헤더의 alg를 읽습니다. 그것이 검증자의 지원 집합 밖에 있는 경우, 그 항목은 지원되지 않음(아래 참조)이며 — 레코드에 대한 오류가 아닙니다.
  3. 키 해석. 위의 경로 1 / 경로 2 판별을 적용하여 32바이트 Ed25519 공개 키를 얻습니다. 어떤 경로로도 얻지 못하면, 그 항목은 미해석입니다.
  4. 재구성 및 검증. to_signSig_structure = ["Signature1", protected, h'', to_sign]을 다시 만들어 정준 CBOR로 인코딩하고, 엄격한 Ed25519로 서명을 검증합니다. (비보호 헤더가 "hashed": true를 운반하는 경우, 먼저 to_signBlake2b-224(to_sign)으로 치환합니다.)
  5. 지갑 바인딩(경로 2 한정). 해석한 키로부터 스테이크 주소를 재계산하여 보호 헤더의 address와 바이트 단위로 비교합니다. 불일치하면 Ed25519 서명 자체는 검증되었더라도 바인딩은 실패합니다. 이 경로 2 한정 확인이 바로 UI가 레코드를 지갑에 바인딩된 것으로 표시할 수 있게 하는 근거이며, 경로 1 항목은 이를 건너뜁니다.

엄격한 Ed25519

검증은 RFC 8032 §5.1.7엄격한 규칙을 따릅니다 — 주어진 키, 메시지, 서명의 어떤 조합에 대해서도 수용 가능한 답은 정확히 하나뿐입니다.

  • R 또는 서명 스칼라 S의 비정준 인코딩(특히 군 위수 에 대한 S ≥ ℓ)은 거부해야 합니다(MUST).
  • 소위수 / 소부분군 / 비틀림 성분을 가진 공개 키와 R 값은 거부해야 합니다(MUST).
  • 보조인자가 적용된 검증 방정식(ZIP-215 / 배치에 적합한 형태)을 엄격한 방정식 대신 사용해서는 안 됩니다(MUST NOT).

판정을 구현 간에 재현 가능하게 만드는 것은 바로 이 엄격성입니다. 보조인자가 적용된 검증자는 엄격한 검증자가 거부하는 서명을 받아들이므로, 두 적합 검증자가 서로 어긋나게 됩니다. 구현은 엄격하고 보조인자를 사용하지 않는 검증을 수행하는 라이브러리 — 또는 라이브러리 모드 — 를 선택해야 합니다.

판정 의미론

서명은 부가적이므로, 검증할 수 없는 서명은 해당 항목에 보고될 뿐 레코드 수준의 실패로 격상되지 않습니다. 각 sigs[i]는 다음의 타입화된 항목별 결과 중 하나로 해석됩니다. 전체 오류 카탈로그와 레코드 수준의 판정 규칙은 검증에 있습니다.

결과의미
verified엄격한 Ed25519(경로 2의 경우 주소 바인딩까지)가 통과했습니다.
signature unsupported보호 헤더의 alg가 검증자의 집합 밖에 있습니다. 정보일 뿐, 결코 오류가 아닙니다.
signer key unresolved허용된 어떤 경로로도 32바이트 Ed25519 공개 키를 얻지 못했습니다.
signature invalid재구성한 Sig_structure에 대해 엄격한 Ed25519가 false를 반환했습니다.
wallet address mismatch경로 2: 서명은 검증되었으나, 재계산한 스테이크 주소 ≠ 주장된 주소.

지원되지 않는 서명이 증명을 무효화하는 일은 결코 없습니다

인식되지 않거나 지원되지 않는 서명 알고리즘은 정보 수준 심각도의 타입화된 signature-unsupported 결과를 낳습니다. 콘텐츠와 타임스탬프 주장 — 온체인의 hashes 커밋먼트 — 은 검증자가 어떤 서명 알고리즘을 구현하든 관계없이 구조적으로 유효합니다. 미래 알고리즘 서명만 담은 레코드라 하더라도 여전히 유효한 존재 증명으로 드러나며, 그러한 각 항목에는 지원되지 않음 태그가 붙습니다. 서명은 부가적이며, 존재는 그것에 의존하지 않습니다.

관련 페이지

  • — Ed25519 서명 키, 그 파생, 그리고 경로 1 kid에 운반되는 32바이트 공개 키.
  • 레코드 — 최상위 sigs 필드, 닫힌 sig-entry 맵(cose_sign1 / cose_key가 각각 단일 바이트 문자열), 그리고 본문 전체의 운반.
  • 검증 — 항목별 결과 코드, 레코드 수준의 판정 규칙, 그리고 전체 검증 파이프라인.