Questa è una traduzione a scopo informativo. Fa fede la versione inglese, che è quella normativa. Leggi la versione inglese

Firme

L'array opzionale `sigs` a livello di record — una COSE_Sign1 distaccata sull'intero corpo del record, il suo payload firmato con separazione di dominio, le due modalità di trasporto della chiave del firmatario e la verifica Ed25519 rigorosa.

Un record Label 309 PUÒ contenere una o più firme di paternità in un array sigs opzionale di primo livello. Ogni voce è una COSE_Sign1 (RFC 9052) distaccata sul corpo del record, che attesta come una determinata chiave garantisca per quel record. La paternità è sempre opzionale: lo standard non richiede mai una firma, e un record privo del campo sigs è una prova di esistenza (Proof of Existence) completa e pienamente verificabile.

Una firma è additiva: aggiunge "e questa chiave ne garantisce" alla rivendicazione temporale, senza mai sostituirla. L'hash del contenuto è la rivendicazione primaria; la firma è un metadato su chi sta dietro a tale rivendicazione. È fondamentale capire che una firma che il verificatore non è in grado di controllare (un algoritmo non supportato, una chiave non risolvibile) non invalida mai la rivendicazione sul contenuto o sull'orario. Le firme falliscono in modo morbido; l'esistenza no.

Questa pagina definisce che cosa copre una firma, i byte esatti che vengono firmati, le due modalità con cui viene trasportata la chiave pubblica del firmatario e la verifica rigorosa svolta da un verificatore pubblico. La chiave Ed25519 in sé è definita nella pagina Chiavi; il campo sigs on-wire — dove cose_sign1 e cose_key sono ciascuno un'unica stringa di byte CBOR — è definito nella pagina Il record.

Che cosa copre una firma

Una singola voce sigs[i] attesta l'intero corpo del record, in modo uniforme. Non esiste granularità di firma per singolo item, per singolo URI o per singolo campo: una firma si impegna su ogni item, ogni URI di storage, ogni busta di cifratura, il puntatore supersedes se presente, e ogni chiave di estensione che il record contiene. Un relay non può aggiungere, rimuovere o riscrivere nessuno di questi elementi a posteriori senza invalidare la firma.

Il corpo firmato è la mappa del record con il campo sigs rimosso: remove_keys(record_map, ["sigs"]), qui indicato come record_body. L'array sigs è escluso da ciò che ogni voce firma perché una firma non può coprire se stessa, e perché ogni firmatario si impegna solo sulla rivendicazione, non sull'elenco dei co-firmatari. Concretamente, ogni voce firma {v, items?, merkle?, supersedes?, crit?, <extensions?>} (gli stessi byte di record_body per tutte le voci) ma nessuna voce firma le altre voci in sigs. Un firmatario attesta quindi che il corpo che ha firmato è il corpo a cui è vincolata ogni altra voce; nessun firmatario attesta quali altri firmatari abbiano co-firmato.

L'ambito della firma è il corpo del record, non la transazione

Una firma verificata dimostra che una chiave ha prodotto una firma sul corpo del record. Non dimostra che la stessa chiave abbia inviato la transazione che lo trasporta, ne abbia pagato la commissione o ne abbia scelto il block time. Un corpo di record identico PUÒ essere ripubblicato da chiunque in una transazione successiva: questa è una portabilità del record intenzionale. Presenta una firma verificata come "firmato da <chiave>", mai come "<chiave> ha inviato questo" o "pubblicato da <chiave> alle <ora>".

Il payload firmato

Ogni voce trasporta una COSE_Sign1 distaccata, quindi il campo payload di COSE è vuoto e i byte effettivamente firmati vengono ricostruiti dal verificatore a partire dal record on-chain (sulla blockchain). Il firmatario calcola:

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_body è serializzato come CBOR canonico secondo RFC 8949 §4.2.1, la stessa codifica deterministica usata dall'intero record. È proprio il determinismo a rendere una firma interoperabile: due implementazioni che codificano lo stesso corpo logico producono record_body_bytes identici byte per byte, così che una firma prodotta da una si verifica con l'altra.

Il prefisso di separazione di dominio

to_sign è la stringa UTF-8 di 25 byte cardano-poe-record-sig-v1 anteposta a record_body_bytes. Il prefisso lega la firma al suo ruolo in Label 309 e previene il replay tra protocolli diversi. Un futuro schema di metadati Cardano che avesse per caso la stessa forma CBOR del corpo (stesse chiavi, stessi tipi) non potrebbe riutilizzare contro se stesso una firma Label 309: il suo to_sign porterebbe un prefisso diverso, o nessuno, quindi la sequenza di byte firmati differirebbe e la firma fallirebbe. Le implementazioni DEVONO inserire questa sequenza letterale di byte esattamente come byte iniziali di to_sign; firmare solo il CBOR canonico nudo, senza prefisso, non è conforme.

Perché external_aad è vuoto

Label 309 colloca il separatore di dominio dentro to_sign, non in external_aad di COSE. Lo slot external_aad (Sig_structure[2]) è sempre la stringa di byte vuota h''. Si tratta di una deviazione deliberata dal consueto schema COSE, che inserisce una stringa di dominio in external_aad, e la ragione è l'interoperabilità con i wallet: CIP-30 signData, il percorso standard di firma tramite wallet su Cardano, stabilisce che non si usi alcun external_aad e non offre a una dApp alcun modo per fornirne uno. Un external_aad non vuoto farebbe fallire ogni firma prodotta da un wallet. Inserire il prefisso nel payload preserva la medesima proprietà anti-replay mantenendo identici, byte per byte, i byte prodotti dal wallet e quelli ricalcolati dal verificatore.

La Sig_structure

Sig_structure è l'array di firma COSE_Sign1 a 4 elementi definito in RFC 9052 §4.4:

SlotValoreNote
[0]"Signature1"Identificatore di contesto COSE fisso, emesso come stringa di testo CBOR completa (11 byte), mai come UTF-8 nudo.
[1]protectedI byte dell'header protetto del firmatario, incapsulati in bstr e in CBOR canonico, usati verbatim e mai ri-canonicalizzati dal verificatore.
[2]external_aadSempre h'' (bstr di lunghezza zero).
[3]to_signIl prefisso di 25 byte concatenato con record_body_bytes.

La COSE_Sign1 pubblicata trasporta il proprio campo payload (COSE_Sign1[2]) come CBOR null (0xF6): la forma distaccata. Un payload allegato, inclusa una stringa di byte di lunghezza zero, viene rifiutato. Distaccare il payload è ciò che ancora i byte firmati al corpo del record che il verificatore ricalcola in modo indipendente; una forma allegata permetterebbe a un produttore di firmare byte presi a prestito che non hanno alcuna relazione con le rivendicazioni on-chain.

Modalità hashed degli hardware wallet

CIP-30 / CIP-8 definiscono un flag opzionale "hashed": true nell'header non protetto, che un co-firmatario hardware con risorse limitate può impostare. Quando è presente e vale true, Sig_structure[3] è il digest Blake2b-224(to_sign) di 28 byte invece di to_sign stesso; gli altri tre slot restano invariati. Un verificatore DEVE ispezionare l'header non protetto ed eseguire questa sostituzione prima della verifica Ed25519 rigorosa. I produttori software e SDK NON DOVREBBERO impostarlo: non risparmia byte on-wire e complica i percorsi di codice del verificatore.

Algoritmo di firma

L'unico algoritmo di firma in v1 è EdDSA su Ed25519 (RFC 8032), identificato da COSE alg = -8 (RFC 9053 §2.2), che risiede nell'header protetto della COSE_Sign1. La baseline obbligatoria di un verificatore v1 è {-8}; può inoltre accettare -19 (Ed25519, fully-specified) e verificare entrambi i codepoint sulla stessa primitiva Ed25519. Il registro è estensibile: le revisioni future aggiungono firme post-quantistiche in modo additivo, mai come modifica incompatibile.

Risoluzione della chiave del firmatario

Un verificatore pubblico deve risolvere la chiave pubblica del firmatario senza contattare alcun servizio, quindi ogni firma porta con sé la propria chiave, o un riferimento inequivocabile ad essa interno alla firma, on-chain. In v1 esistono esattamente due modalità di trasporto, e sono mutuamente esclusive all'interno di una singola voce: una voce che le usa entrambe è un errore strutturale.

Modalità 1 — firma con identità (kid interno alla firma)

La chiave pubblica Ed25519 grezza di 32 byte è collocata all'etichetta di header COSE 4 (kid, RFC 9052 §3.1) dentro l'header protetto della COSE_Sign1. La voce non porta alcun campo cose_key. Per convenzione di Label 309, un kid nell'header protetto lungo esattamente 32 byte è la chiave pubblica, non un puntatore opaco a una chiave da cercare fuori banda. La lunghezza di 32 byte è un discriminante inequivocabile: le chiavi pubbliche Ed25519 sono sempre di 32 byte. Collocare la chiave nell'header protetto (non in quello non protetto) la lega alla firma; un avversario che la riscrivesse invaliderebbe la verifica.

Questa convenzione è una deviazione deliberata e documentata dalla lettura di kid come identificatore opaco prevista in RFC 9052; è ciò che rende la modalità con identità indipendente dai servizi, senza richiedere alcuna directory di chiavi. Il modello delle chiavi è definito nella pagina Chiavi.

Modalità 2 — firma con wallet (cose_key inline)

Una firma CIP-30 signData restituisce la chiave pubblica del firmatario come blob cbor<COSE_Key> separato, non all'interno della COSE_Sign1. Un produttore che inserisce una firma di questo tipo in un record DEVE collocare quella COSE_Key nella stessa voce sigs[i] sotto la chiave cose_key, come un'unica stringa di byte CBOR. Il verificatore la decodifica come COSE_Key e legge la chiave pubblica Ed25519 dall'etichetta -2. La COSE_Key DEVE descrivere solo la metà pubblica (kty = OKP (1), crv = Ed25519 (6), la x di 32 byte all'etichetta -2) e NON DEVE contenere materiale di chiave privata (etichetta -4 e simili); pubblicare uno scalare privato su un registro permanente è una fuga di chiave irreversibile.

Mutua esclusione

Le due modalità sono esclusive a livello di wire. Una voce porta o un kid di 32 byte nell'header protetto e nessun cose_key (modalità 1), oppure un campo cose_key e nessun kid di 32 byte nell'header protetto (modalità 2), mai entrambi. Una voce che porta entrambi viene rifiutata; il verificatore non deve mai disambiguare in fase di verifica. La risoluzione è quindi una discriminazione a livello di wire, non una precedenza per priorità:

ModalitàCondizioneChiave del firmatario
1kid protetto di 32 byte, nessun cose_keyIl valore kid di 32 byte, usato direttamente.
2cose_key presente, nessun kid di 32 byteLa chiave Ed25519 all'etichetta COSE_Key -2.

Un kid trasportato solo nell'header non protetto non è una modalità di risoluzione ammessa: si trova al di fuori della busta firmata, quindi un relay potrebbe riscriverlo senza invalidare la firma. Un verificatore DEVE ignorare i valori kid dell'header non protetto ai fini della risoluzione. Se nessuna modalità consentita produce una chiave Ed25519 di 32 byte, la voce è segnalata come non risolta e non contribuisce ad alcuna rivendicazione di paternità.

Verifica

Un verificatore pubblico controlla ogni sigs[i] in modo indipendente, in quest'ordine:

  1. Decodifica. Analizza la stringa di byte sigs[i].cose_sign1 come una COSE_Sign1. Il campo payload DEVE essere null (distaccato); qualsiasi payload non null o non vuoto è malformato.
  2. Algoritmo. Leggi l'alg dell'header protetto. Se è al di fuori dell'insieme supportato dal verificatore, la voce è non supportata (vedi sotto), non un errore sul record.
  3. Risolvi la chiave. Applica la discriminazione modalità 1 / modalità 2 di cui sopra per ottenere la chiave pubblica Ed25519 di 32 byte. Se nessuna modalità ne produce una, la voce è non risolta.
  4. Ricostruisci e verifica. Ricostruisci to_sign e Sig_structure = ["Signature1", protected, h'', to_sign], codificalo in CBOR canonico e verifica la firma con Ed25519 rigoroso. (Sostituisci prima Blake2b-224(to_sign) a to_sign se l'header non protetto porta "hashed": true.)
  5. Binding del wallet (solo modalità 2). Ricalcola lo stake address dalla chiave risolta e confrontalo byte per byte con l'address dell'header protetto; una discrepanza fa fallire il binding anche se la firma Ed25519 in sé è stata verificata. Questo controllo, presente solo nella modalità 2, è ciò che consente a una UI di presentare un record come legato a un wallet; le voci di modalità 1 lo saltano.

Ed25519 rigoroso

La verifica segue le regole rigorose di RFC 8032 §5.1.7: esiste esattamente una risposta accettabile per ogni data combinazione di chiave, messaggio e firma:

  • Le codifiche non canoniche di R o dello scalare di firma S (in particolare qualsiasi S ≥ ℓ, l'ordine del gruppo) DEVONO essere rifiutate.
  • Le chiavi pubbliche e i valori R di ordine piccolo / sottogruppo piccolo / con componente di torsione DEVONO essere rifiutati.
  • L'equazione di verifica con cofattore (la forma in stile ZIP-215, adatta alla verifica in batch) NON DEVE essere sostituita all'equazione rigorosa.

È il rigore a rendere il verdetto riproducibile tra implementazioni diverse: un verificatore con cofattore accetterebbe firme che uno rigoroso rifiuta, quindi due verificatori conformi non sarebbero d'accordo. Le implementazioni devono scegliere una libreria, o una modalità di libreria, che esegua una verifica rigorosa e senza cofattore.

Semantica del verdetto

Le firme sono additive, quindi una firma non verificabile è segnalata sulla voce, non promossa a un fallimento a livello di record. Ogni sigs[i] si risolve in uno di questi esiti tipizzati per voce; il catalogo completo degli errori e le regole del verdetto a livello di record si trovano nella pagina Verifica:

EsitoSignificato
verificataEd25519 rigoroso (e, per la modalità 2, il binding dell'address) ha avuto successo.
firma non supportataL'alg dell'header protetto è al di fuori dell'insieme del verificatore. Informativo, mai un errore.
chiave del firmatario non risoltaNessuna modalità consentita produce una chiave pubblica Ed25519 di 32 byte.
firma non validaEd25519 rigoroso ha restituito false sulla Sig_structure ricostruita.
address del wallet non corrispondenteModalità 2: la firma è stata verificata, ma lo stake address ricalcolato ≠ quello dichiarato.

Una firma non supportata non invalida mai la prova

Un algoritmo di firma non riconosciuto o non supportato produce un esito tipizzato di firma non supportata con gravità informativa. La rivendicazione sul contenuto e sull'orario (l'impegno hashes on-chain) è strutturalmente valida indipendentemente da quali algoritmi di firma un verificatore implementi. Un record che porta solo firme con algoritmi futuri si presenta comunque come una prova di esistenza valida, con ciascuna di queste voci contrassegnata come non supportata. Le firme sono additive; l'esistenza non dipende da esse.

Pagine correlate

  • Chiavi — la chiave di firma Ed25519, la sua derivazione e la chiave pubblica di 32 byte trasportata nel kid della modalità 1.
  • Il record — il campo sigs di primo livello, la mappa chiusa sig-entry (cose_sign1 / cose_key ciascuno un'unica stringa di byte) e il trasporto sull'intero corpo.
  • Verifica — i codici di esito per ogni voce, le regole del verdetto a livello di record e l'intera pipeline di validazione.