Skip to content

Commit d522bcc

Browse files
authored
fix!: require V2 signatures (#180)
* fix: require V2 signatures This is part of deprecation described in ipfs/js-ipfs#4197 - record creation continues to create both V1 and V2 signatures - record validation no longer accepts V1 signatures Meaning: - modern nodes are 100% V2, they ignore V1 signatures - legacy nodes (go-ipfs < v0.9.0, js-ipfs before Jun 2021) are still able to resolve names created by js-ipns, because V1 is still present BREAKING CHANGE: IPNS V1 signatures are ignored, records without V2 signature are no longer marked as Valid.
1 parent 0f5340a commit d522bcc

File tree

4 files changed

+32
-14
lines changed

4 files changed

+32
-14
lines changed

README.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# ipns <!-- omit in toc -->
22

3-
[![ipfs.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io)
4-
[![IRC](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
3+
[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech)
54
[![Discord](https://img.shields.io/discord/806902334369824788?style=flat-square)](https://discord.gg/ipfs)
65
[![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipns.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipns)
76
[![CI](https://img.shields.io/github/workflow/status/ipfs/js-ipns/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/ipfs/js-ipns/actions/workflows/js-test-and-release.yml)
@@ -126,7 +125,7 @@ const validator = ipns.validator
126125

127126
Contains an object with `validate (marshalledData, key)` and `select (dataA, dataB)` functions.
128127

129-
The `validate` async function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (signature and validity are verified).
128+
The `validate` async function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (`signatureV2` of CBOR `data` is verified).
130129

131130
The `select` function is responsible for deciding which ipns record is the best (newer) between two records. Both records are unmarshalled and their sequence numbers are compared. If the first record provided is the newer, the operation result will be `0`, otherwise the operation result will be `1`.
132131

@@ -151,10 +150,12 @@ Returns a `Promise` that resolves to an object with the entry's properties eg:
151150
```js
152151
{
153152
value: Uint8Array,
154-
signature: Uint8Array,
153+
signature: Uint8Array, // V1 (legacy, ignored)
155154
validityType: 0,
156155
validity: Uint8Array,
157-
sequence: 2
156+
sequence: 2,
157+
signatureV2: Uint8Array, // V2 signature of data field
158+
data: Uint8Array // DAG-CBOR that was signed
158159
}
159160
```
160161

src/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const _create = async (peerId: PeerId, value: Uint8Array, seq: number | bigint,
9595
}
9696

9797
const privateKey = await unmarshalPrivateKey(peerId.privateKey)
98-
const signatureV1 = await sign(privateKey, value, validityType, isoValidity)
98+
const signatureV1 = await signLegacyV1(privateKey, value, validityType, isoValidity)
9999
const data = createCborData(value, isoValidity, validityType, seq, ttl)
100100
const sigData = ipnsEntryDataForV2Sig(data)
101101
const signatureV2 = await privateKey.sign(sigData)
@@ -144,9 +144,9 @@ export { peerIdToRoutingKey } from './utils.js'
144144
export { peerIdFromRoutingKey } from './utils.js'
145145

146146
/**
147-
* Sign ipns record data
147+
* Sign ipns record data using the legacy V1 signature scheme
148148
*/
149-
const sign = async (privateKey: PrivateKey, value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array) => {
149+
const signLegacyV1 = async (privateKey: PrivateKey, value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array) => {
150150
try {
151151
const dataForSignature = ipnsEntryDataForV1Sig(value, validityType, validity)
152152

src/validator.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import errCode from 'err-code'
22
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
33
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
44
import { IpnsEntry } from './pb/ipns.js'
5-
import { parseRFC3339, extractPublicKey, ipnsEntryDataForV1Sig, ipnsEntryDataForV2Sig, unmarshal, peerIdFromRoutingKey, parseCborData } from './utils.js'
5+
import { parseRFC3339, extractPublicKey, ipnsEntryDataForV2Sig, unmarshal, peerIdFromRoutingKey, parseCborData } from './utils.js'
66
import * as ERRORS from './errors.js'
77
import type { IPNSEntry } from './index.js'
88
import type { PublicKey } from '@libp2p/interface-keys'
@@ -27,8 +27,7 @@ export const validate = async (publicKey: PublicKey, entry: IPNSEntry) => {
2727

2828
validateCborDataMatchesPbData(entry)
2929
} else {
30-
signature = entry.signature ?? new Uint8Array(0)
31-
dataForSignature = ipnsEntryDataForV1Sig(value, validityType, validity)
30+
throw errCode(new Error('missing data or signatureV2'), ERRORS.ERR_SIGNATURE_VERIFICATION)
3231
}
3332

3433
// Validate Signature

test/index.spec.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,35 @@ describe('ipns', function () {
6060
await ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))
6161
})
6262

63-
it('should validate a v1 message', async () => {
63+
it('should fail to validate a v1 (deprecated legacy) message', async () => {
6464
const sequence = 0
6565
const validity = 1000000
6666

6767
const entry = await ipns.create(peerId, cid, sequence, validity)
6868

69-
// extra fields added for v2 sigs
69+
// remove the extra fields added for v2 sigs
7070
delete entry.data
7171
delete entry.signatureV2
7272

73-
await ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))
73+
// confirm a v1 exists
74+
expect(entry).to.have.property('signature')
75+
76+
await expect(ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))).to.eventually.be.rejected().with.property('code', ERRORS.ERR_SIGNATURE_VERIFICATION)
77+
})
78+
79+
it('should fail to validate a v2 without v2 signature (ignore v1)', async () => {
80+
const sequence = 0
81+
const validity = 1000000
82+
83+
const entry = await ipns.create(peerId, cid, sequence, validity)
84+
85+
// remove v2 sig
86+
delete entry.signatureV2
87+
88+
// confirm a v1 exists
89+
expect(entry).to.have.property('signature')
90+
91+
await expect(ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))).to.eventually.be.rejected().with.property('code', ERRORS.ERR_SIGNATURE_VERIFICATION)
7492
})
7593

7694
it('should fail to validate a bad record', async () => {

0 commit comments

Comments
 (0)