diff --git a/README.md b/README.md index 1203347..1a446d0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # ipns -[![ipfs.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io) -[![IRC](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) [![Discord](https://img.shields.io/discord/806902334369824788?style=flat-square)](https://discord.gg/ipfs) [![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipns.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipns) [![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 Contains an object with `validate (marshalledData, key)` and `select (dataA, dataB)` functions. -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). +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). 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`. @@ -151,10 +150,12 @@ Returns a `Promise` that resolves to an object with the entry's properties eg: ```js { value: Uint8Array, - signature: Uint8Array, + signature: Uint8Array, // V1 (legacy, ignored) validityType: 0, validity: Uint8Array, - sequence: 2 + sequence: 2, + signatureV2: Uint8Array, // V2 signature of data field + data: Uint8Array // DAG-CBOR that was signed } ``` diff --git a/src/index.ts b/src/index.ts index 5d08559..fcb4332 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,7 +95,7 @@ const _create = async (peerId: PeerId, value: Uint8Array, seq: number | bigint, } const privateKey = await unmarshalPrivateKey(peerId.privateKey) - const signatureV1 = await sign(privateKey, value, validityType, isoValidity) + const signatureV1 = await signLegacyV1(privateKey, value, validityType, isoValidity) const data = createCborData(value, isoValidity, validityType, seq, ttl) const sigData = ipnsEntryDataForV2Sig(data) const signatureV2 = await privateKey.sign(sigData) @@ -144,9 +144,9 @@ export { peerIdToRoutingKey } from './utils.js' export { peerIdFromRoutingKey } from './utils.js' /** - * Sign ipns record data + * Sign ipns record data using the legacy V1 signature scheme */ -const sign = async (privateKey: PrivateKey, value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array) => { +const signLegacyV1 = async (privateKey: PrivateKey, value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array) => { try { const dataForSignature = ipnsEntryDataForV1Sig(value, validityType, validity) diff --git a/src/validator.ts b/src/validator.ts index 70a5a51..c4ef3b5 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -2,7 +2,7 @@ import errCode from 'err-code' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { IpnsEntry } from './pb/ipns.js' -import { parseRFC3339, extractPublicKey, ipnsEntryDataForV1Sig, ipnsEntryDataForV2Sig, unmarshal, peerIdFromRoutingKey, parseCborData } from './utils.js' +import { parseRFC3339, extractPublicKey, ipnsEntryDataForV2Sig, unmarshal, peerIdFromRoutingKey, parseCborData } from './utils.js' import * as ERRORS from './errors.js' import type { IPNSEntry } from './index.js' import type { PublicKey } from '@libp2p/interface-keys' @@ -27,8 +27,7 @@ export const validate = async (publicKey: PublicKey, entry: IPNSEntry) => { validateCborDataMatchesPbData(entry) } else { - signature = entry.signature ?? new Uint8Array(0) - dataForSignature = ipnsEntryDataForV1Sig(value, validityType, validity) + throw errCode(new Error('missing data or signatureV2'), ERRORS.ERR_SIGNATURE_VERIFICATION) } // Validate Signature diff --git a/test/index.spec.ts b/test/index.spec.ts index 0cbe097..eafca97 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -60,17 +60,35 @@ describe('ipns', function () { await ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry)) }) - it('should validate a v1 message', async () => { + it('should fail to validate a v1 (deprecated legacy) message', async () => { const sequence = 0 const validity = 1000000 const entry = await ipns.create(peerId, cid, sequence, validity) - // extra fields added for v2 sigs + // remove the extra fields added for v2 sigs delete entry.data delete entry.signatureV2 - await ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry)) + // confirm a v1 exists + expect(entry).to.have.property('signature') + + await expect(ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))).to.eventually.be.rejected().with.property('code', ERRORS.ERR_SIGNATURE_VERIFICATION) + }) + + it('should fail to validate a v2 without v2 signature (ignore v1)', async () => { + const sequence = 0 + const validity = 1000000 + + const entry = await ipns.create(peerId, cid, sequence, validity) + + // remove v2 sig + delete entry.signatureV2 + + // confirm a v1 exists + expect(entry).to.have.property('signature') + + await expect(ipnsValidator(peerIdToRoutingKey(peerId), marshal(entry))).to.eventually.be.rejected().with.property('code', ERRORS.ERR_SIGNATURE_VERIFICATION) }) it('should fail to validate a bad record', async () => {