From 64c1398b4869fdb9068d6ca69214d2685ae67c56 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Mon, 20 Nov 2023 11:04:59 +0300 Subject: [PATCH 001/110] varsig dummy pkg --- packages/varsig/.eslintrc.json | 11 ++ packages/varsig/.gitignore | 4 + packages/varsig/LICENSE-APACHE | 5 + packages/varsig/LICENSE-MIT | 19 ++ packages/varsig/README.md | 42 +++++ packages/varsig/jest.config.json | 22 +++ packages/varsig/package.json | 54 ++++++ packages/varsig/src/index.ts | 280 +++++++++++++++++++++++++++++ packages/varsig/test/index.test.ts | 103 +++++++++++ packages/varsig/tsconfig.json | 7 + packages/varsig/tsconfig.lint.json | 4 + pnpm-lock.yaml | 15 +- 12 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 packages/varsig/.eslintrc.json create mode 100644 packages/varsig/.gitignore create mode 100644 packages/varsig/LICENSE-APACHE create mode 100644 packages/varsig/LICENSE-MIT create mode 100644 packages/varsig/README.md create mode 100644 packages/varsig/jest.config.json create mode 100644 packages/varsig/package.json create mode 100644 packages/varsig/src/index.ts create mode 100644 packages/varsig/test/index.test.ts create mode 100644 packages/varsig/tsconfig.json create mode 100644 packages/varsig/tsconfig.lint.json diff --git a/packages/varsig/.eslintrc.json b/packages/varsig/.eslintrc.json new file mode 100644 index 00000000..4dbcd387 --- /dev/null +++ b/packages/varsig/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "extends": ["3box", "3box/jest", "3box/typescript"], + "parserOptions": { + "project": ["tsconfig.lint.json"] + }, + "rules": { + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/interface-name-prefix": "off" + } +} \ No newline at end of file diff --git a/packages/varsig/.gitignore b/packages/varsig/.gitignore new file mode 100644 index 00000000..4c9d7c35 --- /dev/null +++ b/packages/varsig/.gitignore @@ -0,0 +1,4 @@ +*.log +.DS_Store +node_modules +dist diff --git a/packages/varsig/LICENSE-APACHE b/packages/varsig/LICENSE-APACHE new file mode 100644 index 00000000..14478a3b --- /dev/null +++ b/packages/varsig/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/packages/varsig/LICENSE-MIT b/packages/varsig/LICENSE-MIT new file mode 100644 index 00000000..749aa1ec --- /dev/null +++ b/packages/varsig/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/packages/varsig/README.md b/packages/varsig/README.md new file mode 100644 index 00000000..cf3e23b9 --- /dev/null +++ b/packages/varsig/README.md @@ -0,0 +1,42 @@ +# Multidid +Multidid is a representation strategy for DIDs and DID URLs that is very compact and extensible. It allows any DID method to be represented as a string of bytes. Reference [specification](https://github.com/ChainAgnostic/multidid). + +This library is a multidid utility library to encode and decode multidids to their byte and string representation and convert from did strings to multidid representations. + +## Installation + +``` +npm install --save @didtools/multidid +``` + +## Usage + +```js +import { Multidid } from '@didtools/multidid' + +const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + +// Multidid instance from did string +const multidid = Multidid.fromString(didString) + +// Encode to bytes +multidid.toBytes() + +// Decode from bytes to multidid instance +Multidid.fromBytes(bytes) + +// Encode as a multibase base16 string +const mdStr = multidid.toMultibase('base16') +console.log(mdStr) +//f9d1aed013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29307a364d6b6954427a31796d75657041513448454859534631483871754735474c5656515233646a6458336d446f6f5770 + +// Multidid instance from base encoded string +Multidid.fromMultibase(mdStr) + +// DID string from multidid +multidid.toString() +``` + +## License + +Apache-2.0 OR MIT diff --git a/packages/varsig/jest.config.json b/packages/varsig/jest.config.json new file mode 100644 index 00000000..53bb7cd5 --- /dev/null +++ b/packages/varsig/jest.config.json @@ -0,0 +1,22 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "testRegex": ".(spec|test).ts$", + "testEnvironment": "node", + "extensionsToTreatAsEsm": [".ts"], + "globals": { + "ts-jest": { + "useESM": true + } + }, + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.js$": "$1" + }, + "transform": { + "^.+\\.(t|j)s$": [ + "@swc/jest", + { + "root": "../.." + } + ] + } +} \ No newline at end of file diff --git a/packages/varsig/package.json b/packages/varsig/package.json new file mode 100644 index 00000000..c8bdf5cc --- /dev/null +++ b/packages/varsig/package.json @@ -0,0 +1,54 @@ +{ + "name": "@didtools/varsig", + "version": "0.0.1", + "author": "3Box Labs", + "license": "(Apache-2.0 OR MIT)", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": "./dist/index.js" + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=14.14" + }, + "sideEffects": false, + "scripts": { + "build:clean": "del dist", + "build:js": "swc src -d ./dist --config-file ../../.swcrc", + "build:types": "tsc --emitDeclarationOnly --skipLibCheck", + "build": "pnpm run build:clean && pnpm run build:types && pnpm run build:js", + "lint": "eslint src --fix", + "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js", + "test:ci": "pnpm run test --ci --coverage", + "prepare": "pnpm run build", + "prepublishOnly": "package-check", + "size": "./node_modules/.bin/size-limit", + "analyze": "./node_modules/.bin/size-limit --why" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ceramicnetwork/js-did.git" + }, + "keywords": [ + "DID", + "identity", + "did-provider", + "self-sovereign", + "multiformat" + ], + "bugs": { + "url": "https://github.com/ceramicnetwork/js-did/issues" + }, + "homepage": "https://github.com/ceramicnetwork/js-did#readme", + "devDependencies": { + "@stablelib/random": "^1.0.2" + }, + "dependencies": { + "multiformats": "^11.0.2", + "uint8arrays": "^4.0.3" + } +} diff --git a/packages/varsig/src/index.ts b/packages/varsig/src/index.ts new file mode 100644 index 00000000..ed58e141 --- /dev/null +++ b/packages/varsig/src/index.ts @@ -0,0 +1,280 @@ +/** + * # Multidid + * Multidid is a representation strategy for DIDs and DID URLs that is very compact and extensible. It allows any DID method to be represented as a + * string of bytes. Reference [specification](https://github.com/ChainAgnostic/multidid). + * + * This library is a multidid utility library to encode and decode multidids to their byte and string representation and convert from did strings to multidid representations. + * + * ## Installation + * + * ``` + * npm install --save @didtools/multidid + * ``` + * + * ## Usage + * + * ```js + * import { Multidid } from '@didtools/multidid' + * + * const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + * + * // Multidid instance from did string + * const multidid = Multidid.fromString(didString) + * + * // Encode to bytes + * multidid.toBytes() + * + * // Decode from bytes to multidid instance + * Multidid.fromBytes(bytes) + * + * // Encode as base16 string + * const mdStr = multidid.toMultibase('base16') + * console.log(mdStr) + * // f9d1aed013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29307a364d6b6954427a31796d75657041513448454859534631483871754735474c5656515233646a6458336d446f6f5770 + * + * // Multidid instance from base encoded string + * Multidid.fromMultibase(mdStr) + * + * // DID string from multidid + * multidid.toString() + * + * ``` + * + * @module @didtools/multidid + */ + +import * as u8a from 'uint8arrays' +import { alloc } from 'uint8arrays/alloc' +import { varint } from 'multiformats' +import { base58btc } from 'multiformats/bases/base58' +import { base16 } from 'multiformats/bases/base16' + +/** + * Multicodec Codes https://github.com/multiformats/multicodec/blob/master/table.csv + */ +const MULTIDID_CODEC = 0xd1d // did +const ANY_METHOD_CODE = 0x55 // raw +const PKH_METHOD_CODE = 0xca // Chain Agnostic +const SECP256K1_CODE = 0xe7 // secp256k1-pub +const BLS12_381_G2_CODE = 0xeb // bls12_381-g2-pub +const X25519_CODE = 0xec // x25519-pub +const ED25519_CODE = 0xed // ed25519-pub +const P256_CODE = 0x1200 // p256-pub +const P384_CODE = 0x1201 // p384-pub +const P521_CODE = 0x1202 // p521-pub +const RSA_CODE = 0x1205 // rsa-pub + +/** + * did:key length table + */ +const KEY_METHOD_CODES_LENGTH: Record = { + [SECP256K1_CODE]: 33, + [BLS12_381_G2_CODE]: 96, + [X25519_CODE]: 32, + [ED25519_CODE]: 32, + [P256_CODE]: 33, + [P384_CODE]: 49, + [P521_CODE]: 67, +} + +// 2048-bit modulus, public exponent 65537 +const RSA_270_PREFIX = new Uint8Array([48, 130, 1, 10, 2, 130, 1, 1, 0]) +const RSA_270 = 270 +// 4096-bit modulus, public exponent 65537 +const RSA_526_PREFIX = new Uint8Array([48, 130, 2, 10, 2, 130, 2, 1, 0]) +const RSA_526 = 526 +const keyPrefixByteLen = 9 + +const RSACodeLen = (keyPrefix: Uint8Array): number => { + if (u8a.equals(keyPrefix, RSA_270_PREFIX)) return RSA_270 + if (u8a.equals(keyPrefix, RSA_526_PREFIX)) return RSA_526 + throw new Error('Not a valid RSA did:key') +} + +const keyMethodCodeLen = (code: number, key: Uint8Array): number => { + let methodIdLen = KEY_METHOD_CODES_LENGTH[code] + if (!methodIdLen) { + if (code !== RSA_CODE) throw new Error('did:key type not found') + methodIdLen = RSACodeLen(key) + } + + return methodIdLen +} + +const isKeyMethodCode = (code: number): boolean => { + return Object.keys(KEY_METHOD_CODES_LENGTH).includes(code.toString()) || code === RSA_CODE +} + +type SupportedBase = 'base16' | 'base58btc' + +type InspectObject = { + methodCode: number + methodIdBytes: Uint8Array + urlBytes: Uint8Array +} + +export class Multidid { + private mdBytes: Uint8Array | null + + /** + * @param {number} code - DID Method Codec + * @param {Uint8Array} id - DID method id + * @param {Uint8Array} url - DID Method url portion + * + */ + constructor(private code: number, private id: Uint8Array, private url: Uint8Array) { + this.mdBytes = null + } + + private encode(): Uint8Array { + const methodCodeOffset = varint.encodingLength(MULTIDID_CODEC) + const methodIdOffset = methodCodeOffset + varint.encodingLength(this.code) + + let methodIdLen + if (this.code === ANY_METHOD_CODE) { + methodIdLen = 0 + } else if (this.code === PKH_METHOD_CODE) { + throw new Error('TODO') + } else { + methodIdLen = keyMethodCodeLen(this.code, this.id.slice(0, keyPrefixByteLen)) + } + + if (!methodIdLen && methodIdLen !== 0) throw new Error('Not matching did method code found') + if (methodIdLen !== this.id.byteLength) + throw new Error('Length of method id does not match expected length') + + const urlLenOffset = methodIdOffset + methodIdLen + const urlBytesOffset = urlLenOffset + varint.encodingLength(this.url.byteLength) + + const bytes = alloc(urlBytesOffset + this.url.byteLength) + varint.encodeTo(MULTIDID_CODEC, bytes, 0) + varint.encodeTo(this.code, bytes, methodCodeOffset) + bytes.set(this.id, methodIdOffset) + varint.encodeTo(this.url.byteLength, bytes, urlLenOffset) + bytes.set(this.url, urlBytesOffset) + + return bytes + } + + /** + * Decoded a multidid from its binary representation + */ + static fromBytes(bytes: Uint8Array): Multidid { + const [, didCodeLen] = varint.decode(bytes, 0) + const [methodCode, methodCodeLen] = varint.decode(bytes, didCodeLen) + const methodIdOffset = didCodeLen + methodCodeLen + + let methodIdLen + if (methodCode === ANY_METHOD_CODE) { + methodIdLen = 0 + } else if (methodCode === PKH_METHOD_CODE) { + throw new Error('TODO') + } else { + const prefix = bytes.slice(methodIdOffset, methodIdOffset + keyPrefixByteLen) + methodIdLen = keyMethodCodeLen(methodCode, prefix) + } + if (!methodIdLen && methodIdLen !== 0) throw new Error('Not matching did method code found') + + const methodId = alloc(methodIdLen) + methodId.set(bytes.slice(methodIdOffset, methodIdOffset + methodIdLen)) + const urlLenOffset = methodIdOffset + methodIdLen + const [urlLen, urlLenLen] = varint.decode(bytes, urlLenOffset) + const urlBytesOffset = urlLenOffset + urlLenLen + const url = alloc(urlLen) + url.set(bytes.slice(urlBytesOffset, urlBytesOffset + urlLen)) + return new Multidid(methodCode, methodId, url) + } + + /** + * Encode multidid to bytes + */ + toBytes(): Uint8Array { + if (!this.mdBytes) this.mdBytes = this.encode() + return this.mdBytes + } + + /** + * Encode multidid as multibase string, defaults to base58btc, multibase prefix string + */ + toMultibase(base: SupportedBase = 'base58btc'): string { + const encoder = base === 'base58btc' ? base58btc : base16 + return encoder.encode(this.toBytes()) + } + + /** + * Decode multibase multidid string into instance, expects multibase prefix + */ + static fromMultibase(multidid: string): Multidid { + let bytes + switch (multidid.slice(0, 1)) { + case base58btc.prefix: { + bytes = base58btc.decode(multidid) + break + } + case base16.prefix: { + bytes = base16.decode(multidid) + break + } + default: { + throw new Error('Multibase encoding not found, base58btc and base16 supported') + } + } + return this.fromBytes(bytes) + } + + /** + * Decode multidid instance from a did string + */ + static fromString(did: string): Multidid { + const [, method, suffix] = did.split(':') + const [id, url] = suffix.split(/(?=[#?/])(.*)/) + switch (method) { + case 'key': { + const keyBytes = base58btc.decode(id) + const [code, codeLen] = varint.decode(keyBytes, 0) + const urlBytes = u8a.fromString(url || '', 'utf8') + const idBytes = keyBytes.slice(codeLen) + return new Multidid(code, idBytes, urlBytes) + } + case 'pkh': { + throw new Error('TODO') + } + default: { + const urlBytes = u8a.fromString(`${method}:${suffix}`, 'utf8') + return new Multidid(ANY_METHOD_CODE, alloc(0), urlBytes) + } + } + } + + /** + * DID string from multidid + */ + toString(): string { + if (this.code === ANY_METHOD_CODE) { + return `did:${u8a.toString(this.url, 'utf8')}` + } else if (this.code === PKH_METHOD_CODE) { + throw new Error('TODO') + } else if (isKeyMethodCode(this.code)) { + const methodIdOffset = varint.encodingLength(this.code) + const prefix = this.id.slice(0, keyPrefixByteLen) + const methodIdLen = keyMethodCodeLen(this.code, prefix) + const key = alloc(methodIdLen + methodIdOffset) + varint.encodeTo(this.code, key, 0) + key.set(this.id, methodIdOffset) + return `did:key:${base58btc.encode(key)}${u8a.toString(this.url, 'utf8')}` + } else { + throw new Error('Unable to convert to did string, no matching method') + } + } + + /** + * Get the multidid by parts, res.methodCode, res.methodIdBytes, res.urlBytes + */ + inspect(): InspectObject { + return { + methodCode: this.code, + methodIdBytes: this.id, + urlBytes: this.url, + } + } +} diff --git a/packages/varsig/test/index.test.ts b/packages/varsig/test/index.test.ts new file mode 100644 index 00000000..40fc2214 --- /dev/null +++ b/packages/varsig/test/index.test.ts @@ -0,0 +1,103 @@ +import { Multidid } from '../src/index.js' + +describe('@didtools/multidid', () => { + + it('did string roundtrip did:key, no url portion', () => { + const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + const mdid = Multidid.fromString(didString) + expect(mdid.toString()).toEqual(didString) + }) + + it('did string roundtrip did:key, with url portion', () => { + const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + const mdid = Multidid.fromString(didString) + expect(mdid.toString()).toEqual(didString) + }) + + it('did string roundtrip did:*, no url portion', () => { + const didString = "did:example:123456" + const mdid = Multidid.fromString(didString) + expect(mdid.toString()).toEqual(didString) + }) + + it('did string roundtrip did:*, with url portion', () => { + const didString = "did:example:123456?versionId=1" + const mdid = Multidid.fromString(didString) + expect(mdid.toString()).toEqual(didString) + }) + + it('spefication did:* vector 1', () => { + const didString = "did:example:123456" + const hexMD = 'f9d1a550e6578616d706c653a313233343536' + const mdid1 = Multidid.fromMultibase(hexMD) + const mdid2 = Multidid.fromString(didString) + expect(mdid1.toMultibase('base16')).toEqual(hexMD) + expect(mdid1.toString()).toEqual(didString) + expect(mdid2.toMultibase('base16')).toEqual(hexMD) + expect(mdid2.toString()).toEqual(didString) + expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) + }) + + it('spefication did:* vector 2', () => { + const didString = "did:example:123456?versionId=1" + const hexMD = 'f9d1a551a6578616d706c653a3132333435363f76657273696f6e49643d31' + const mdid1 = Multidid.fromMultibase(hexMD) + const mdid2 = Multidid.fromString(didString) + expect(mdid1.toMultibase('base16')).toEqual(hexMD) + expect(mdid1.toString()).toEqual(didString) + expect(mdid2.toMultibase('base16')).toEqual(hexMD) + expect(mdid2.toString()).toEqual(didString) + expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) + }) + + it('spefication did:key vector 1', () => { + const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + const hexMD = 'f9d1aed013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2900' + const mdid1 = Multidid.fromMultibase(hexMD) + const mdid2 = Multidid.fromString(didString) + expect(mdid1.toMultibase('base16')).toEqual(hexMD) + expect(mdid1.toString()).toEqual(didString) + expect(mdid2.toMultibase('base16')).toEqual(hexMD) + expect(mdid2.toString()).toEqual(didString) + expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) + }) + + it('spefication did:key vector 2', () => { + const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + const hexMD = 'f9d1aed013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2931237a364d6b6954427a31796d75657041513448454859534631483871754735474c5656515233646a6458336d446f6f5770' + const mdid1 = Multidid.fromMultibase(hexMD) + const mdid2 = Multidid.fromString(didString) + expect(mdid1.toMultibase('base16')).toEqual(hexMD) + expect(mdid1.toString()).toEqual(didString) + expect(mdid2.toMultibase('base16')).toEqual(hexMD) + expect(mdid2.toString()).toEqual(didString) + expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) + }) + + it('spefication did:key vector 3', () => { + const didString = "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme" + const hexMD = 'f9d1ae70103874c15c7fda20e539c6e5ba573c139884c351188799f5458b4b41f7924f235cd00' + const mdid1 = Multidid.fromMultibase(hexMD) + const mdid2 = Multidid.fromString(didString) + expect(mdid1.toMultibase('base16')).toEqual(hexMD) + expect(mdid1.toString()).toEqual(didString) + expect(mdid2.toMultibase('base16')).toEqual(hexMD) + expect(mdid2.toString()).toEqual(didString) + expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) + }) + + //RSA test vectors https://w3c-ccg.github.io/did-method-key/#rsa-2048 + it('spefication did:key RSA 2048-bit', () => { + const didString = "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i" + const mdid = Multidid.fromString(didString) + expect(mdid.toString()).toEqual(didString) + expect(mdid.inspect().methodIdBytes.byteLength).toEqual(270) + }) + + it('spefication did:key RSA 4096-bit', () => { + const didString = "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2" + const mdid = Multidid.fromString(didString) + expect(mdid.toString()).toEqual(didString) + expect(mdid.inspect().methodIdBytes.byteLength).toEqual(526) + }) +}) \ No newline at end of file diff --git a/packages/varsig/tsconfig.json b/packages/varsig/tsconfig.json new file mode 100644 index 00000000..34756dd4 --- /dev/null +++ b/packages/varsig/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/packages/varsig/tsconfig.lint.json b/packages/varsig/tsconfig.lint.json new file mode 100644 index 00000000..e9041fd6 --- /dev/null +++ b/packages/varsig/tsconfig.lint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e15fcb2..add20171 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -571,6 +571,19 @@ importers: specifier: ^0.3.1 version: 0.3.1 + packages/varsig: + dependencies: + multiformats: + specifier: ^11.0.2 + version: 11.0.2 + uint8arrays: + specifier: ^4.0.3 + version: 4.0.3 + devDependencies: + '@stablelib/random': + specifier: ^1.0.2 + version: 1.0.2 + website: dependencies: '@docusaurus/core': From 94074daac7d07cb62d18f235ec6c1adf4d15e375 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Mon, 20 Nov 2023 12:41:48 +0300 Subject: [PATCH 002/110] bytes --- .editorconfig | 14 ++ packages/varsig/.eslintrc.json | 11 - packages/varsig/package.json | 4 +- packages/varsig/src/bytes.ts | 49 +++++ packages/varsig/src/index.ts | 280 -------------------------- packages/varsig/src/varbytes.ts | 17 ++ packages/varsig/test/index.test.ts | 103 ---------- packages/varsig/test/varbytes.test.ts | 32 +++ pnpm-lock.yaml | 10 + 9 files changed, 125 insertions(+), 395 deletions(-) create mode 100644 .editorconfig delete mode 100644 packages/varsig/.eslintrc.json create mode 100644 packages/varsig/src/bytes.ts delete mode 100644 packages/varsig/src/index.ts create mode 100644 packages/varsig/src/varbytes.ts create mode 100644 packages/varsig/test/varbytes.test.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7051c25c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/packages/varsig/.eslintrc.json b/packages/varsig/.eslintrc.json deleted file mode 100644 index 4dbcd387..00000000 --- a/packages/varsig/.eslintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": ["3box", "3box/jest", "3box/typescript"], - "parserOptions": { - "project": ["tsconfig.lint.json"] - }, - "rules": { - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/ban-ts-ignore": "off", - "@typescript-eslint/interface-name-prefix": "off" - } -} \ No newline at end of file diff --git a/packages/varsig/package.json b/packages/varsig/package.json index c8bdf5cc..85b40e82 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -48,7 +48,9 @@ "@stablelib/random": "^1.0.2" }, "dependencies": { + "codeco": "^1.1.0", "multiformats": "^11.0.2", - "uint8arrays": "^4.0.3" + "uint8arrays": "^4.0.3", + "varintes": "^2.0.5" } } diff --git a/packages/varsig/src/bytes.ts b/packages/varsig/src/bytes.ts new file mode 100644 index 00000000..f520b92f --- /dev/null +++ b/packages/varsig/src/bytes.ts @@ -0,0 +1,49 @@ +import { left, right, type Either } from 'codeco/either' +import * as varintes from 'varintes' + +type VarsigBytes = { + encoding: number + hashing: number + signing: number + signature: Uint8Array +} + +const VARSIG_SIGIL = 0x34 +const VARSIG_SIGIL_BYTES = new Uint8Array([VARSIG_SIGIL]) + +export function fromBytes(bytes: Uint8Array): Either { + const [sigil, sigilRead] = varintes.decode(bytes) + if (sigil !== VARSIG_SIGIL) return left(new Error(`Wrong sigil`)) + try { + const sigilRemainder = bytes.subarray(sigilRead) + const [encoding, encodingRead] = varintes.decode(sigilRemainder) + const encodingRemainder = bytes.subarray(encodingRead) + const [hashing, hashingRead] = varintes.decode(encodingRemainder) + const hashingRemainder = sigilRemainder.subarray(hashingRead) + const [signing, signingRead] = varintes.decode(hashingRemainder) + const signature = hashingRemainder.subarray(signingRead) + return right({ + encoding: encoding, + hashing: hashing, + signing: signing, + signature: signature, + }) + } catch (e) { + return left(e as Error) + } +} + +export function toBytes(varsig: VarsigBytes): Uint8Array { + const encodingLen = varintes.encodingLength(varsig.encoding) + const hashingLen = varintes.encodingLength(varsig.hashing) + const signingLen = varintes.encodingLength(varsig.signing) + const result = new Uint8Array( + 1 + encodingLen + hashingLen + signingLen + varsig.signature.byteLength + ) + result.set(VARSIG_SIGIL_BYTES, 0) + varintes.encode(varsig.encoding, result, 1) + varintes.encode(varsig.hashing, result, 1 + encodingLen) + varintes.encode(varsig.signing, result, 1 + encodingLen + hashingLen) + result.set(varsig.signature, 1 + encodingLen + hashingLen + signingLen) + return result +} diff --git a/packages/varsig/src/index.ts b/packages/varsig/src/index.ts deleted file mode 100644 index ed58e141..00000000 --- a/packages/varsig/src/index.ts +++ /dev/null @@ -1,280 +0,0 @@ -/** - * # Multidid - * Multidid is a representation strategy for DIDs and DID URLs that is very compact and extensible. It allows any DID method to be represented as a - * string of bytes. Reference [specification](https://github.com/ChainAgnostic/multidid). - * - * This library is a multidid utility library to encode and decode multidids to their byte and string representation and convert from did strings to multidid representations. - * - * ## Installation - * - * ``` - * npm install --save @didtools/multidid - * ``` - * - * ## Usage - * - * ```js - * import { Multidid } from '@didtools/multidid' - * - * const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" - * - * // Multidid instance from did string - * const multidid = Multidid.fromString(didString) - * - * // Encode to bytes - * multidid.toBytes() - * - * // Decode from bytes to multidid instance - * Multidid.fromBytes(bytes) - * - * // Encode as base16 string - * const mdStr = multidid.toMultibase('base16') - * console.log(mdStr) - * // f9d1aed013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29307a364d6b6954427a31796d75657041513448454859534631483871754735474c5656515233646a6458336d446f6f5770 - * - * // Multidid instance from base encoded string - * Multidid.fromMultibase(mdStr) - * - * // DID string from multidid - * multidid.toString() - * - * ``` - * - * @module @didtools/multidid - */ - -import * as u8a from 'uint8arrays' -import { alloc } from 'uint8arrays/alloc' -import { varint } from 'multiformats' -import { base58btc } from 'multiformats/bases/base58' -import { base16 } from 'multiformats/bases/base16' - -/** - * Multicodec Codes https://github.com/multiformats/multicodec/blob/master/table.csv - */ -const MULTIDID_CODEC = 0xd1d // did -const ANY_METHOD_CODE = 0x55 // raw -const PKH_METHOD_CODE = 0xca // Chain Agnostic -const SECP256K1_CODE = 0xe7 // secp256k1-pub -const BLS12_381_G2_CODE = 0xeb // bls12_381-g2-pub -const X25519_CODE = 0xec // x25519-pub -const ED25519_CODE = 0xed // ed25519-pub -const P256_CODE = 0x1200 // p256-pub -const P384_CODE = 0x1201 // p384-pub -const P521_CODE = 0x1202 // p521-pub -const RSA_CODE = 0x1205 // rsa-pub - -/** - * did:key length table - */ -const KEY_METHOD_CODES_LENGTH: Record = { - [SECP256K1_CODE]: 33, - [BLS12_381_G2_CODE]: 96, - [X25519_CODE]: 32, - [ED25519_CODE]: 32, - [P256_CODE]: 33, - [P384_CODE]: 49, - [P521_CODE]: 67, -} - -// 2048-bit modulus, public exponent 65537 -const RSA_270_PREFIX = new Uint8Array([48, 130, 1, 10, 2, 130, 1, 1, 0]) -const RSA_270 = 270 -// 4096-bit modulus, public exponent 65537 -const RSA_526_PREFIX = new Uint8Array([48, 130, 2, 10, 2, 130, 2, 1, 0]) -const RSA_526 = 526 -const keyPrefixByteLen = 9 - -const RSACodeLen = (keyPrefix: Uint8Array): number => { - if (u8a.equals(keyPrefix, RSA_270_PREFIX)) return RSA_270 - if (u8a.equals(keyPrefix, RSA_526_PREFIX)) return RSA_526 - throw new Error('Not a valid RSA did:key') -} - -const keyMethodCodeLen = (code: number, key: Uint8Array): number => { - let methodIdLen = KEY_METHOD_CODES_LENGTH[code] - if (!methodIdLen) { - if (code !== RSA_CODE) throw new Error('did:key type not found') - methodIdLen = RSACodeLen(key) - } - - return methodIdLen -} - -const isKeyMethodCode = (code: number): boolean => { - return Object.keys(KEY_METHOD_CODES_LENGTH).includes(code.toString()) || code === RSA_CODE -} - -type SupportedBase = 'base16' | 'base58btc' - -type InspectObject = { - methodCode: number - methodIdBytes: Uint8Array - urlBytes: Uint8Array -} - -export class Multidid { - private mdBytes: Uint8Array | null - - /** - * @param {number} code - DID Method Codec - * @param {Uint8Array} id - DID method id - * @param {Uint8Array} url - DID Method url portion - * - */ - constructor(private code: number, private id: Uint8Array, private url: Uint8Array) { - this.mdBytes = null - } - - private encode(): Uint8Array { - const methodCodeOffset = varint.encodingLength(MULTIDID_CODEC) - const methodIdOffset = methodCodeOffset + varint.encodingLength(this.code) - - let methodIdLen - if (this.code === ANY_METHOD_CODE) { - methodIdLen = 0 - } else if (this.code === PKH_METHOD_CODE) { - throw new Error('TODO') - } else { - methodIdLen = keyMethodCodeLen(this.code, this.id.slice(0, keyPrefixByteLen)) - } - - if (!methodIdLen && methodIdLen !== 0) throw new Error('Not matching did method code found') - if (methodIdLen !== this.id.byteLength) - throw new Error('Length of method id does not match expected length') - - const urlLenOffset = methodIdOffset + methodIdLen - const urlBytesOffset = urlLenOffset + varint.encodingLength(this.url.byteLength) - - const bytes = alloc(urlBytesOffset + this.url.byteLength) - varint.encodeTo(MULTIDID_CODEC, bytes, 0) - varint.encodeTo(this.code, bytes, methodCodeOffset) - bytes.set(this.id, methodIdOffset) - varint.encodeTo(this.url.byteLength, bytes, urlLenOffset) - bytes.set(this.url, urlBytesOffset) - - return bytes - } - - /** - * Decoded a multidid from its binary representation - */ - static fromBytes(bytes: Uint8Array): Multidid { - const [, didCodeLen] = varint.decode(bytes, 0) - const [methodCode, methodCodeLen] = varint.decode(bytes, didCodeLen) - const methodIdOffset = didCodeLen + methodCodeLen - - let methodIdLen - if (methodCode === ANY_METHOD_CODE) { - methodIdLen = 0 - } else if (methodCode === PKH_METHOD_CODE) { - throw new Error('TODO') - } else { - const prefix = bytes.slice(methodIdOffset, methodIdOffset + keyPrefixByteLen) - methodIdLen = keyMethodCodeLen(methodCode, prefix) - } - if (!methodIdLen && methodIdLen !== 0) throw new Error('Not matching did method code found') - - const methodId = alloc(methodIdLen) - methodId.set(bytes.slice(methodIdOffset, methodIdOffset + methodIdLen)) - const urlLenOffset = methodIdOffset + methodIdLen - const [urlLen, urlLenLen] = varint.decode(bytes, urlLenOffset) - const urlBytesOffset = urlLenOffset + urlLenLen - const url = alloc(urlLen) - url.set(bytes.slice(urlBytesOffset, urlBytesOffset + urlLen)) - return new Multidid(methodCode, methodId, url) - } - - /** - * Encode multidid to bytes - */ - toBytes(): Uint8Array { - if (!this.mdBytes) this.mdBytes = this.encode() - return this.mdBytes - } - - /** - * Encode multidid as multibase string, defaults to base58btc, multibase prefix string - */ - toMultibase(base: SupportedBase = 'base58btc'): string { - const encoder = base === 'base58btc' ? base58btc : base16 - return encoder.encode(this.toBytes()) - } - - /** - * Decode multibase multidid string into instance, expects multibase prefix - */ - static fromMultibase(multidid: string): Multidid { - let bytes - switch (multidid.slice(0, 1)) { - case base58btc.prefix: { - bytes = base58btc.decode(multidid) - break - } - case base16.prefix: { - bytes = base16.decode(multidid) - break - } - default: { - throw new Error('Multibase encoding not found, base58btc and base16 supported') - } - } - return this.fromBytes(bytes) - } - - /** - * Decode multidid instance from a did string - */ - static fromString(did: string): Multidid { - const [, method, suffix] = did.split(':') - const [id, url] = suffix.split(/(?=[#?/])(.*)/) - switch (method) { - case 'key': { - const keyBytes = base58btc.decode(id) - const [code, codeLen] = varint.decode(keyBytes, 0) - const urlBytes = u8a.fromString(url || '', 'utf8') - const idBytes = keyBytes.slice(codeLen) - return new Multidid(code, idBytes, urlBytes) - } - case 'pkh': { - throw new Error('TODO') - } - default: { - const urlBytes = u8a.fromString(`${method}:${suffix}`, 'utf8') - return new Multidid(ANY_METHOD_CODE, alloc(0), urlBytes) - } - } - } - - /** - * DID string from multidid - */ - toString(): string { - if (this.code === ANY_METHOD_CODE) { - return `did:${u8a.toString(this.url, 'utf8')}` - } else if (this.code === PKH_METHOD_CODE) { - throw new Error('TODO') - } else if (isKeyMethodCode(this.code)) { - const methodIdOffset = varint.encodingLength(this.code) - const prefix = this.id.slice(0, keyPrefixByteLen) - const methodIdLen = keyMethodCodeLen(this.code, prefix) - const key = alloc(methodIdLen + methodIdOffset) - varint.encodeTo(this.code, key, 0) - key.set(this.id, methodIdOffset) - return `did:key:${base58btc.encode(key)}${u8a.toString(this.url, 'utf8')}` - } else { - throw new Error('Unable to convert to did string, no matching method') - } - } - - /** - * Get the multidid by parts, res.methodCode, res.methodIdBytes, res.urlBytes - */ - inspect(): InspectObject { - return { - methodCode: this.code, - methodIdBytes: this.id, - urlBytes: this.url, - } - } -} diff --git a/packages/varsig/src/varbytes.ts b/packages/varsig/src/varbytes.ts new file mode 100644 index 00000000..cdd9b413 --- /dev/null +++ b/packages/varsig/src/varbytes.ts @@ -0,0 +1,17 @@ +import * as varintes from 'varintes' + +export function encode(bytes: Uint8Array, out?: Uint8Array, offset?: number): [Uint8Array, number] { + const byteLength = bytes.byteLength + const [lenBytes, lenLen] = varintes.encode(byteLength) + const destination = out ? out.subarray(offset) : new Uint8Array(lenLen + byteLength) + destination.set(lenBytes, 0) + destination.set(bytes, lenLen) + return [destination, lenLen + byteLength] +} + +export function decode(buffer: Uint8Array, offset = 0): [Uint8Array, number] { + const bytes = buffer.subarray(offset) + const [len, lenLen] = varintes.decode(bytes) + const result = bytes.subarray(lenLen, len + lenLen) + return [result, result.byteLength] +} diff --git a/packages/varsig/test/index.test.ts b/packages/varsig/test/index.test.ts index 40fc2214..e69de29b 100644 --- a/packages/varsig/test/index.test.ts +++ b/packages/varsig/test/index.test.ts @@ -1,103 +0,0 @@ -import { Multidid } from '../src/index.js' - -describe('@didtools/multidid', () => { - - it('did string roundtrip did:key, no url portion', () => { - const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" - const mdid = Multidid.fromString(didString) - expect(mdid.toString()).toEqual(didString) - }) - - it('did string roundtrip did:key, with url portion', () => { - const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" - const mdid = Multidid.fromString(didString) - expect(mdid.toString()).toEqual(didString) - }) - - it('did string roundtrip did:*, no url portion', () => { - const didString = "did:example:123456" - const mdid = Multidid.fromString(didString) - expect(mdid.toString()).toEqual(didString) - }) - - it('did string roundtrip did:*, with url portion', () => { - const didString = "did:example:123456?versionId=1" - const mdid = Multidid.fromString(didString) - expect(mdid.toString()).toEqual(didString) - }) - - it('spefication did:* vector 1', () => { - const didString = "did:example:123456" - const hexMD = 'f9d1a550e6578616d706c653a313233343536' - const mdid1 = Multidid.fromMultibase(hexMD) - const mdid2 = Multidid.fromString(didString) - expect(mdid1.toMultibase('base16')).toEqual(hexMD) - expect(mdid1.toString()).toEqual(didString) - expect(mdid2.toMultibase('base16')).toEqual(hexMD) - expect(mdid2.toString()).toEqual(didString) - expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) - }) - - it('spefication did:* vector 2', () => { - const didString = "did:example:123456?versionId=1" - const hexMD = 'f9d1a551a6578616d706c653a3132333435363f76657273696f6e49643d31' - const mdid1 = Multidid.fromMultibase(hexMD) - const mdid2 = Multidid.fromString(didString) - expect(mdid1.toMultibase('base16')).toEqual(hexMD) - expect(mdid1.toString()).toEqual(didString) - expect(mdid2.toMultibase('base16')).toEqual(hexMD) - expect(mdid2.toString()).toEqual(didString) - expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) - }) - - it('spefication did:key vector 1', () => { - const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" - const hexMD = 'f9d1aed013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2900' - const mdid1 = Multidid.fromMultibase(hexMD) - const mdid2 = Multidid.fromString(didString) - expect(mdid1.toMultibase('base16')).toEqual(hexMD) - expect(mdid1.toString()).toEqual(didString) - expect(mdid2.toMultibase('base16')).toEqual(hexMD) - expect(mdid2.toString()).toEqual(didString) - expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) - }) - - it('spefication did:key vector 2', () => { - const didString = "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" - const hexMD = 'f9d1aed013b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2931237a364d6b6954427a31796d75657041513448454859534631483871754735474c5656515233646a6458336d446f6f5770' - const mdid1 = Multidid.fromMultibase(hexMD) - const mdid2 = Multidid.fromString(didString) - expect(mdid1.toMultibase('base16')).toEqual(hexMD) - expect(mdid1.toString()).toEqual(didString) - expect(mdid2.toMultibase('base16')).toEqual(hexMD) - expect(mdid2.toString()).toEqual(didString) - expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) - }) - - it('spefication did:key vector 3', () => { - const didString = "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme" - const hexMD = 'f9d1ae70103874c15c7fda20e539c6e5ba573c139884c351188799f5458b4b41f7924f235cd00' - const mdid1 = Multidid.fromMultibase(hexMD) - const mdid2 = Multidid.fromString(didString) - expect(mdid1.toMultibase('base16')).toEqual(hexMD) - expect(mdid1.toString()).toEqual(didString) - expect(mdid2.toMultibase('base16')).toEqual(hexMD) - expect(mdid2.toString()).toEqual(didString) - expect(mdid1.toMultibase('base58btc')).toEqual(mdid2.toMultibase('base58btc')) - }) - - //RSA test vectors https://w3c-ccg.github.io/did-method-key/#rsa-2048 - it('spefication did:key RSA 2048-bit', () => { - const didString = "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i" - const mdid = Multidid.fromString(didString) - expect(mdid.toString()).toEqual(didString) - expect(mdid.inspect().methodIdBytes.byteLength).toEqual(270) - }) - - it('spefication did:key RSA 4096-bit', () => { - const didString = "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2" - const mdid = Multidid.fromString(didString) - expect(mdid.toString()).toEqual(didString) - expect(mdid.inspect().methodIdBytes.byteLength).toEqual(526) - }) -}) \ No newline at end of file diff --git a/packages/varsig/test/varbytes.test.ts b/packages/varsig/test/varbytes.test.ts new file mode 100644 index 00000000..8ff94ca8 --- /dev/null +++ b/packages/varsig/test/varbytes.test.ts @@ -0,0 +1,32 @@ +import { test, expect } from '@jest/globals' +import * as varintes from 'varintes' +import * as varbytes from '../src/varbytes.js' +import { randomBytes } from '@stablelib/random' +import { concat } from 'uint8arrays' + +test('single-byte len', () => { + const input = new Uint8Array([20]) + const [encoded] = varbytes.encode(input) + expect(encoded).toEqual(new Uint8Array([1, 20])) + const [decoded] = varbytes.decode(encoded) + expect(decoded).toEqual(input) +}) + +test('multi-byte len', () => { + const input = randomBytes(256) + const [encoded] = varbytes.encode(input) + const [len] = varintes.encode(input.byteLength) + expect(encoded).toEqual(concat([len, input])) + const [decoded] = varbytes.decode(encoded) + expect(decoded).toEqual(input) +}) + +test('with remainder', () => { + const input = randomBytes(256) + const [encoded] = varbytes.encode(input) + const full = concat([encoded, randomBytes(256)]) + const [len] = varintes.encode(input.byteLength) + expect(encoded).toEqual(concat([len, input])) + const [decoded] = varbytes.decode(full) + expect(decoded).toEqual(input) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index add20171..1ac80bbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -573,12 +573,18 @@ importers: packages/varsig: dependencies: + codeco: + specifier: ^1.1.0 + version: 1.1.0 multiformats: specifier: ^11.0.2 version: 11.0.2 uint8arrays: specifier: ^4.0.3 version: 4.0.3 + varintes: + specifier: ^2.0.5 + version: 2.0.5 devDependencies: '@stablelib/random': specifier: ^1.0.2 @@ -18799,6 +18805,10 @@ packages: resolution: {integrity: sha512-4Ls542xejhl3tnbUnrZVf/q0ApH3rj7hVRLue2mKDreiXyPFaOP/T0k0elfi+63pcVF18zchT/R/RBAUbnon0A==} dev: false + /varintes@2.0.5: + resolution: {integrity: sha512-iF3jlHLko9NrYjaUZvT3VwypP3V20KNNhT1tzqblyIyrVjNiW7HseGOhuP+apgZBp9X/8+5pxa7kNikhJeZlIw==} + dev: false + /varuint-bitcoin@1.1.2: resolution: {integrity: sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==} dependencies: From d8ac024a24c85b6ccebe0de6c8832157b704c3d8 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Mon, 20 Nov 2023 13:44:14 +0300 Subject: [PATCH 003/110] dummy sig --- packages/varsig/package.json | 1 + packages/varsig/src/bytes.ts | 33 ++++++++++++----------------- packages/varsig/src/varsig.ts | 17 +++++++++++++++ packages/varsig/test/varsig.test.ts | 23 ++++++++++++++++++++ pnpm-lock.yaml | 3 +++ 5 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 packages/varsig/src/varsig.ts create mode 100644 packages/varsig/test/varsig.test.ts diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 85b40e82..870a8ce7 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -48,6 +48,7 @@ "@stablelib/random": "^1.0.2" }, "dependencies": { + "@types/node": "^20.2.3", "codeco": "^1.1.0", "multiformats": "^11.0.2", "uint8arrays": "^4.0.3", diff --git a/packages/varsig/src/bytes.ts b/packages/varsig/src/bytes.ts index f520b92f..483c3195 100644 --- a/packages/varsig/src/bytes.ts +++ b/packages/varsig/src/bytes.ts @@ -1,4 +1,3 @@ -import { left, right, type Either } from 'codeco/either' import * as varintes from 'varintes' type VarsigBytes = { @@ -11,25 +10,21 @@ type VarsigBytes = { const VARSIG_SIGIL = 0x34 const VARSIG_SIGIL_BYTES = new Uint8Array([VARSIG_SIGIL]) -export function fromBytes(bytes: Uint8Array): Either { +export function fromBytes(bytes: Uint8Array): VarsigBytes { const [sigil, sigilRead] = varintes.decode(bytes) - if (sigil !== VARSIG_SIGIL) return left(new Error(`Wrong sigil`)) - try { - const sigilRemainder = bytes.subarray(sigilRead) - const [encoding, encodingRead] = varintes.decode(sigilRemainder) - const encodingRemainder = bytes.subarray(encodingRead) - const [hashing, hashingRead] = varintes.decode(encodingRemainder) - const hashingRemainder = sigilRemainder.subarray(hashingRead) - const [signing, signingRead] = varintes.decode(hashingRemainder) - const signature = hashingRemainder.subarray(signingRead) - return right({ - encoding: encoding, - hashing: hashing, - signing: signing, - signature: signature, - }) - } catch (e) { - return left(e as Error) + if (sigil !== VARSIG_SIGIL) throw new Error(`Wrong sigil`) + const sigilRemainder = bytes.subarray(sigilRead) + const [encoding, encodingRead] = varintes.decode(sigilRemainder) + const encodingRemainder = bytes.subarray(encodingRead) + const [hashing, hashingRead] = varintes.decode(encodingRemainder) + const hashingRemainder = sigilRemainder.subarray(hashingRead) + const [signing, signingRead] = varintes.decode(hashingRemainder) + const signature = hashingRemainder.subarray(signingRead) + return { + encoding: encoding, + hashing: hashing, + signing: signing, + signature: signature, } } diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts new file mode 100644 index 00000000..e167c047 --- /dev/null +++ b/packages/varsig/src/varsig.ts @@ -0,0 +1,17 @@ +export enum SIGNING { + RSA = 0x1205, +} + +export enum HASHING { + SHA2_256 = 0x12, + SHA2_512 = 0x13, +} + +export enum ENCODING { + IDENTITY = 0x00, +} + +// export function verify(signingInput: Uint8Array, bytes: Uint8Array, offset = 0) { +// const input = bytes.subarray(offset) +// const varsig = fromBytes(input) +// } diff --git a/packages/varsig/test/varsig.test.ts b/packages/varsig/test/varsig.test.ts new file mode 100644 index 00000000..c2e649ee --- /dev/null +++ b/packages/varsig/test/varsig.test.ts @@ -0,0 +1,23 @@ +import { test } from '@jest/globals' +import {toBytes} from "../src/bytes"; + +test('rsa', async () => { + const key = await crypto.subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 4096, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: 'SHA-256', + }, + true, + ['sign', 'verify'] + ) + const a = await crypto.subtle.sign( + { + name: 'RSASSA-PKCS1-v1_5', + }, + key.privateKey, + new Uint8Array([1, 2, 3]) + ) + console.log('a', new Uint8Array(a)) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ac80bbd..da8f408c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -573,6 +573,9 @@ importers: packages/varsig: dependencies: + '@types/node': + specifier: ^20.2.3 + version: 20.2.3 codeco: specifier: ^1.1.0 version: 1.1.0 From b7ae7599327782364db757fb88e24992933d2630 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Mon, 20 Nov 2023 14:17:36 +0300 Subject: [PATCH 004/110] varsig --- packages/varsig/src/varsig.ts | 15 +++++++++++++++ packages/varsig/test/varsig.test.ts | 13 +++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index e167c047..23fab9fa 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -11,6 +11,21 @@ export enum ENCODING { IDENTITY = 0x00, } +export type Varsig = { + encoding: ENCODING + hashing: HASHING + signing: SIGNING + payload: Uint8Array +} + +// Sign an IPLD +// Here is EIP1271 blob please encode +// or here is JWT please encode it + +export function sign(key: CryptoKey) { + +} + // export function verify(signingInput: Uint8Array, bytes: Uint8Array, offset = 0) { // const input = bytes.subarray(offset) // const varsig = fromBytes(input) diff --git a/packages/varsig/test/varsig.test.ts b/packages/varsig/test/varsig.test.ts index c2e649ee..8bdd77fd 100644 --- a/packages/varsig/test/varsig.test.ts +++ b/packages/varsig/test/varsig.test.ts @@ -1,5 +1,7 @@ import { test } from '@jest/globals' -import {toBytes} from "../src/bytes"; +import { toBytes } from '../src/bytes.js' +import { ENCODING, HASHING, SIGNING } from '../src/varsig.js' +import * as uint8arrays from 'uint8arrays' test('rsa', async () => { const key = await crypto.subtle.generateKey( @@ -19,5 +21,12 @@ test('rsa', async () => { key.privateKey, new Uint8Array([1, 2, 3]) ) - console.log('a', new Uint8Array(a)) + const signatureBytes = new Uint8Array(a) + const b = toBytes({ + encoding: ENCODING.IDENTITY, + hashing: HASHING.SHA2_256, + signing: SIGNING.RSA, + signature: signatureBytes, + }) + console.log('b', uint8arrays.toString(b, 'hex')) }) From ce26395e582f6e223bb2683859abb47ceb86abe1 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Mon, 20 Nov 2023 14:49:54 +0300 Subject: [PATCH 005/110] wip --- packages/varsig/src/varsig.ts | 7 ++----- packages/varsig/test/varsig.test.ts | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 23fab9fa..37555c63 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -15,17 +15,14 @@ export type Varsig = { encoding: ENCODING hashing: HASHING signing: SIGNING - payload: Uint8Array + payload: Record + signature: Uint8Array } // Sign an IPLD // Here is EIP1271 blob please encode // or here is JWT please encode it -export function sign(key: CryptoKey) { - -} - // export function verify(signingInput: Uint8Array, bytes: Uint8Array, offset = 0) { // const input = bytes.subarray(offset) // const varsig = fromBytes(input) diff --git a/packages/varsig/test/varsig.test.ts b/packages/varsig/test/varsig.test.ts index 8bdd77fd..f0f3ab44 100644 --- a/packages/varsig/test/varsig.test.ts +++ b/packages/varsig/test/varsig.test.ts @@ -1,6 +1,6 @@ import { test } from '@jest/globals' import { toBytes } from '../src/bytes.js' -import { ENCODING, HASHING, SIGNING } from '../src/varsig.js' +import { ENCODING, HASHING, SIGNING, Varsig } from '../src/varsig.js' import * as uint8arrays from 'uint8arrays' test('rsa', async () => { @@ -22,11 +22,24 @@ test('rsa', async () => { new Uint8Array([1, 2, 3]) ) const signatureBytes = new Uint8Array(a) - const b = toBytes({ + const b = toIPLD({ + payload: {}, encoding: ENCODING.IDENTITY, hashing: HASHING.SHA2_256, signing: SIGNING.RSA, signature: signatureBytes, }) - console.log('b', uint8arrays.toString(b, 'hex')) + console.log('b', b) }) + +function toIPLD(input: Varsig) { + return { + ...input.payload, + _signature: toBytes({ + encoding: input.encoding, + hashing: input.hashing, + signing: input.signing, + signature: input.signature, + }), + } +} From 77db8b131c0d915e9a530e856bd7992d6e56f24a Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Mon, 20 Nov 2023 15:05:08 +0300 Subject: [PATCH 006/110] wip --- packages/varsig/package.json | 2 ++ packages/varsig/test/varsig.test.ts | 27 ++++++++++++++++++++++++++- pnpm-lock.yaml | 6 ++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 870a8ce7..6d9207d8 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -50,6 +50,8 @@ "dependencies": { "@types/node": "^20.2.3", "codeco": "^1.1.0", + "key-did-provider-ed25519": "workspace:^", + "key-did-resolver": "workspace:^", "multiformats": "^11.0.2", "uint8arrays": "^4.0.3", "varintes": "^2.0.5" diff --git a/packages/varsig/test/varsig.test.ts b/packages/varsig/test/varsig.test.ts index f0f3ab44..cacfda99 100644 --- a/packages/varsig/test/varsig.test.ts +++ b/packages/varsig/test/varsig.test.ts @@ -2,6 +2,10 @@ import { test } from '@jest/globals' import { toBytes } from '../src/bytes.js' import { ENCODING, HASHING, SIGNING, Varsig } from '../src/varsig.js' import * as uint8arrays from 'uint8arrays' +import { Ed25519Provider } from 'key-did-provider-ed25519' +import KeyResolver from 'key-did-resolver' +import { DID, GeneralJWS } from 'dids' +import { randomBytes } from '@stablelib/random' test('rsa', async () => { const key = await crypto.subtle.generateKey( @@ -29,7 +33,7 @@ test('rsa', async () => { signing: SIGNING.RSA, signature: signatureBytes, }) - console.log('b', b) + // console.log('b', b) }) function toIPLD(input: Varsig) { @@ -43,3 +47,24 @@ function toIPLD(input: Varsig) { }), } } + +function jwsToIPLD(jws: GeneralJWS) { + const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(jws.payload, 'base64url'))) + console.log('payload', payload) + const signature0 = jws.signatures[0] + const protectedHeader = JSON.parse( + uint8arrays.toString(uint8arrays.fromString(signature0.protected, 'base64url')) + ) + console.log('protected', protectedHeader) +} + +test('jwt-to-ipld', async () => { + const seed = randomBytes(32) + const provider = new Ed25519Provider(seed) + const did = new DID({ provider, resolver: KeyResolver.getResolver() }) + await did.authenticate() + const jws = await did.createJWS({ hello: 'world' }) + console.log('jws', jws) + jwsToIPLD(jws) + console.log('dagjws', await did.createDagJWS({ hello: 'world' })) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da8f408c..f69917a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -579,6 +579,12 @@ importers: codeco: specifier: ^1.1.0 version: 1.1.0 + key-did-provider-ed25519: + specifier: workspace:^ + version: link:../key-did-provider-ed25519 + key-did-resolver: + specifier: workspace:^ + version: link:../key-did-resolver multiformats: specifier: ^11.0.2 version: 11.0.2 From 958fba65e07580ab9df34ae437ec974ace8e632d Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Mon, 20 Nov 2023 19:59:57 +0300 Subject: [PATCH 007/110] wip --- packages/varsig/src/encoding/jws.ts | 43 +++++++++++++++++++++++++++++ packages/varsig/src/varsig.ts | 3 ++ pnpm-lock.yaml | 3 ++ 3 files changed, 49 insertions(+) create mode 100644 packages/varsig/src/encoding/jws.ts diff --git a/packages/varsig/src/encoding/jws.ts b/packages/varsig/src/encoding/jws.ts new file mode 100644 index 00000000..a94363ab --- /dev/null +++ b/packages/varsig/src/encoding/jws.ts @@ -0,0 +1,43 @@ +import type { GeneralJWS } from 'dids' +import * as uint8arrays from 'uint8arrays' +import { toBytes } from '../bytes.js' +import { ENCODING, HASHING, SIGNING } from '../varsig.js' + +export function encode(jws: GeneralJWS) { + const signature0 = jws.signatures[0] + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const protectedHeader = JSON.parse( + uint8arrays.toString(uint8arrays.fromString(signature0.protected, 'base64url')) + ) + const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(jws.payload, 'base64url'))) + const signature = uint8arrays.fromString(signature0.signature, 'base64url') + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + switch (protectedHeader.alg) { + case 'EdDSA': { + const sig = toBytes({ + encoding: ENCODING.JWT, + hashing: HASHING.SHA2_256, + signing: SIGNING.ED25519, + signature: signature, + }) + delete protectedHeader.typ + delete protectedHeader.alg + return { + _header: protectedHeader, + ...payload, + _signature: sig, + } + } + case 'ES256': { + const sig = toBytes({ + encoding: ENCODING.JWT, + hashing: HASHING.SHA2_256, + signing: SIGNING.RSA + }) + } + } + return { + _header: {}, + _sig: {}, + } +} diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 37555c63..54f926df 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -1,5 +1,7 @@ export enum SIGNING { RSA = 0x1205, + ED25519 = 0xed, + ES256 = 0x12, } export enum HASHING { @@ -9,6 +11,7 @@ export enum HASHING { export enum ENCODING { IDENTITY = 0x00, + JWT = 0x01, } export type Varsig = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f69917a9..d4b9c02f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -579,6 +579,9 @@ importers: codeco: specifier: ^1.1.0 version: 1.1.0 + dids: + specifier: workspace:^ + version: link:../dids key-did-provider-ed25519: specifier: workspace:^ version: link:../key-did-provider-ed25519 From 13533034b979cadaa40c6b9176f4432079e910b6 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Mon, 20 Nov 2023 21:44:56 +0300 Subject: [PATCH 008/110] Sketch for api interface using eip712 --- packages/varsig/src/encoding/eip712.ts | 119 +++++++++++++++++++ packages/varsig/test/encoding/eip712.test.ts | 0 2 files changed, 119 insertions(+) create mode 100644 packages/varsig/src/encoding/eip712.ts create mode 100644 packages/varsig/test/encoding/eip712.test.ts diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts new file mode 100644 index 00000000..762f2b11 --- /dev/null +++ b/packages/varsig/src/encoding/eip712.ts @@ -0,0 +1,119 @@ +import * as varintes from 'varintes' +import * as uint8arrays from 'uint8arrays' + +type IpldEip712Type = Record + +interface Eip712Domain { + name: string, + version: string, + chainId: number, + verifyingContract: string +} + +interface IpldEip712Meta { + types: Record + primary: string + domain: Eip712Domain +} + +type IpldEip712Message = Record + + +interface Eip712TypeField { + name: string, + type: string +} + +type Eip712Types = Record + +interface Eip712 { + types: Eip712Types + domain: Eip712Domain + primaryType: string + message: Record +} + +type CompressedType = [string, string][] +type CompressedTypes = Record + + +type Canonicalizer = (node: any) => Uint8Array; + +interface CanonicalizerSetup { + remainder: Uint8Array, + canonicalizer: Canonicalizer +} + +export const CODEC = 0xe712 // TODO encode as varint + +export function setupCanonicalizer(varsigReminder: Uint8Array): CanonicalizerSetup { + const [metadataLength, read] = varintes.decode(varsigReminder) + const metadataBytes = metadataLengthRemainder.subarray(read, read + metadataLength) + const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) + + return { + remainder: varsigReminder.subarray(read + metadataLength) + canonicalizer: parameterizeCanonicalizer(metadata) + } +} + +function parameterizeCanonicalizer({ types, primary, domain }: IpldEip712Meta): Canonicalizer { + const decompressTypes = decompressTypes(types) + const domain = domain + const primaryType = primary + return (node) => { + // TODO + } +} + +export function fromEip712({ types, domain, primaryType, message }: Eip712): ({ node: IpldEip712, params: Uint8Array }) { + const metadata = JSON.serialize({ types: compressTypes(types), primary: primaryType, domain }) + const metadataBytes = uint8arrays.fromString(metadata) + const metadataLength = varintes.encode(metadataBytes.length) + return { + node: messageToIpld(message) + params: uint8arrays.concat([metadataLength, metadataBytes]) + } +} + +function messageToIpld(message: Record, types: Eip712Types, selected: string): IpldEip712Message { + const node = {} + for (const [key, value] of Object.entries(message)) { + const type = types[selected].find(({ name }) => name === key + if (!type) throw new Error(`Type for ${key} not found`) + if (type.startsWith('bytes')) { + node[key] = uint8arrays.fromString(value.slice(2), 'base16') + // check if first characther is upper case + } else if (type[0] === type[0].toUpperCase()) { + node[key] = messageToIpld(value, types, type) + } else { + node[key] = value + } + } + return node +} + +const EIP712_DOMAIN = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, +]; + +function compressTypes(types: Eip712Types): CompressedTypes { + const compressed = {} + for (const [key, value] of Object.entries(types)) { + if (key === 'EIP712Domain') continue + compressed[key] = value.map(({ name, type }) => [name, type]) + } + return compressed +} + +function decompressTypes(compressed: CompressedTypes): Eip712Types { + const types = { EIP712Domain: EIP712_DOMAIN } + for (const [key, value] of Object.entries(compressed)) { + types[key] = value.map(([name, type]) => ({ name, type })) + } + return types +} + diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts new file mode 100644 index 00000000..e69de29b From 3052e92e39893320b2abb9f4a9e959007a02fcd9 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Mon, 20 Nov 2023 22:59:08 +0300 Subject: [PATCH 009/110] fix eip712 encoder --- packages/varsig/src/encoding/eip712.ts | 27 +++++-- packages/varsig/test/encoding/eip712.test.ts | 78 ++++++++++++++++++++ pnpm-lock.yaml | 5 +- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index 762f2b11..a54a891f 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -52,14 +52,13 @@ export function setupCanonicalizer(varsigReminder: Uint8Array): CanonicalizerSet const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) return { - remainder: varsigReminder.subarray(read + metadataLength) + remainder: varsigReminder.subarray(read + metadataLength), canonicalizer: parameterizeCanonicalizer(metadata) } } function parameterizeCanonicalizer({ types, primary, domain }: IpldEip712Meta): Canonicalizer { const decompressTypes = decompressTypes(types) - const domain = domain const primaryType = primary return (node) => { // TODO @@ -67,11 +66,11 @@ function parameterizeCanonicalizer({ types, primary, domain }: IpldEip712Meta): } export function fromEip712({ types, domain, primaryType, message }: Eip712): ({ node: IpldEip712, params: Uint8Array }) { - const metadata = JSON.serialize({ types: compressTypes(types), primary: primaryType, domain }) + const metadata = JSON.stringify({ types: compressTypes(types), primary: primaryType, domain }) const metadataBytes = uint8arrays.fromString(metadata) - const metadataLength = varintes.encode(metadataBytes.length) + const metadataLength = varintes.encode(metadataBytes.length)[0] return { - node: messageToIpld(message) + node: messageToIpld(message, types, primaryType), params: uint8arrays.concat([metadataLength, metadataBytes]) } } @@ -79,7 +78,7 @@ export function fromEip712({ types, domain, primaryType, message }: Eip712): ({ function messageToIpld(message: Record, types: Eip712Types, selected: string): IpldEip712Message { const node = {} for (const [key, value] of Object.entries(message)) { - const type = types[selected].find(({ name }) => name === key + const type = types[selected].find(({ name }) => name === key).type if (!type) throw new Error(`Type for ${key} not found`) if (type.startsWith('bytes')) { node[key] = uint8arrays.fromString(value.slice(2), 'base16') @@ -104,7 +103,13 @@ function compressTypes(types: Eip712Types): CompressedTypes { const compressed = {} for (const [key, value] of Object.entries(types)) { if (key === 'EIP712Domain') continue - compressed[key] = value.map(({ name, type }) => [name, type]) + compressed[key] = value.map(({ name, type }) => [ + name, + type + .replace('uint', 'u').replace('int', 'i') + .replace('bytes', 'b').replace('string', 's') + .replace('address', 'a').replace('bool', 'b') + ]) } return compressed } @@ -112,7 +117,13 @@ function compressTypes(types: Eip712Types): CompressedTypes { function decompressTypes(compressed: CompressedTypes): Eip712Types { const types = { EIP712Domain: EIP712_DOMAIN } for (const [key, value] of Object.entries(compressed)) { - types[key] = value.map(([name, type]) => ({ name, type })) + types[key] = value.map(([name, type]) => ({ + name, + type: type + .replace('u', 'uint').replace('i', 'int') + .replace('b', 'bytes').replace('s', 'string') + .replace('a', 'address').replace('b', 'bool') + })) } return types } diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts index e69de29b..326ade80 100644 --- a/packages/varsig/test/encoding/eip712.test.ts +++ b/packages/varsig/test/encoding/eip712.test.ts @@ -0,0 +1,78 @@ +import { fromEip712 } from '../../src/encoding/eip712.ts' + +const testData = { + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + }, + { + "name": "attachment", + "type": "bytes" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!", + "attachment": "0xababababababababababa83459873459873459873498575986734359" + } + } + +test('Encode eip712 message', async () => { + const enc = fromEip712(testData) + + expect(enc.params.length).toEqual(270) + expect(enc.node.attachment instanceof Uint8Array).toBeTruthy() +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4b9c02f..44a3a705 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -579,9 +579,6 @@ importers: codeco: specifier: ^1.1.0 version: 1.1.0 - dids: - specifier: workspace:^ - version: link:../dids key-did-provider-ed25519: specifier: workspace:^ version: link:../key-did-provider-ed25519 From 3591453b7e7ef7de21e807b78c26523fb9c181a3 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Mon, 20 Nov 2023 23:38:00 +0300 Subject: [PATCH 010/110] eip712 more compression --- packages/varsig/src/encoding/eip712.ts | 45 ++++++++++++++------ packages/varsig/test/encoding/eip712.test.ts | 18 +++++++- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index a54a891f..2e10d223 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -30,11 +30,12 @@ interface Eip712 { types: Eip712Types domain: Eip712Domain primaryType: string - message: Record + message?: Record } type CompressedType = [string, string][] type CompressedTypes = Record +type CompressedDomain = [string, string, number, string] type Canonicalizer = (node: any) => Uint8Array; @@ -48,25 +49,27 @@ export const CODEC = 0xe712 // TODO encode as varint export function setupCanonicalizer(varsigReminder: Uint8Array): CanonicalizerSetup { const [metadataLength, read] = varintes.decode(varsigReminder) - const metadataBytes = metadataLengthRemainder.subarray(read, read + metadataLength) - const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) - + const metadataBytes = varsigReminder.subarray(read, read + metadataLength) + const [compTypes, primaryType, domain] = JSON.parse(uint8arrays.toString(metadataBytes)) + const metadata = { + types: decompressTypes(compTypes), + primary: primaryType, + domain: decompressDomain(domain) + } return { remainder: varsigReminder.subarray(read + metadataLength), canonicalizer: parameterizeCanonicalizer(metadata) } } -function parameterizeCanonicalizer({ types, primary, domain }: IpldEip712Meta): Canonicalizer { - const decompressTypes = decompressTypes(types) - const primaryType = primary +function parameterizeCanonicalizer({ types, primary, domain }: Eip712): Canonicalizer { return (node) => { // TODO } } export function fromEip712({ types, domain, primaryType, message }: Eip712): ({ node: IpldEip712, params: Uint8Array }) { - const metadata = JSON.stringify({ types: compressTypes(types), primary: primaryType, domain }) + const metadata = JSON.stringify([ compressTypes(types), primaryType, compressDomain(domain) ]) const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] return { @@ -99,13 +102,26 @@ const EIP712_DOMAIN = [ { name: 'verifyingContract', type: 'address' }, ]; +function compressDomain(domain: Eip712Domain): CompressedDomain { + return [ domain.name, domain.version, domain.chainId, domain.verifyingContract ] +} + +function decompressDomain(domain: CompressedDomain): Eip712Domain { + return { + name: domain[0], + version: domain[1], + chainId: domain[2], + verifyingContract: domain[3] + } +} + function compressTypes(types: Eip712Types): CompressedTypes { const compressed = {} for (const [key, value] of Object.entries(types)) { if (key === 'EIP712Domain') continue compressed[key] = value.map(({ name, type }) => [ name, - type + type // TODO make this more resilient .replace('uint', 'u').replace('int', 'i') .replace('bytes', 'b').replace('string', 's') .replace('address', 'a').replace('bool', 'b') @@ -114,15 +130,18 @@ function compressTypes(types: Eip712Types): CompressedTypes { return compressed } +const FULL_TYPES = { + 'u': 'uint', 'i': 'int', + 'b': 'bytes', 's': 'string', + 'a': 'address', 'b': 'bool' +} + function decompressTypes(compressed: CompressedTypes): Eip712Types { const types = { EIP712Domain: EIP712_DOMAIN } for (const [key, value] of Object.entries(compressed)) { types[key] = value.map(([name, type]) => ({ name, - type: type - .replace('u', 'uint').replace('i', 'int') - .replace('b', 'bytes').replace('s', 'string') - .replace('a', 'address').replace('b', 'bool') + type: FULL_TYPES[type] || type })) } return types diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts index 326ade80..eb219597 100644 --- a/packages/varsig/test/encoding/eip712.test.ts +++ b/packages/varsig/test/encoding/eip712.test.ts @@ -1,4 +1,5 @@ -import { fromEip712 } from '../../src/encoding/eip712.ts' +import { fromEip712, setupCanonicalizer } from '../../src/encoding/eip712.ts' +import * as uint8arrays from 'uint8arrays' const testData = { "types": { @@ -73,6 +74,19 @@ const testData = { test('Encode eip712 message', async () => { const enc = fromEip712(testData) - expect(enc.params.length).toEqual(270) + expect(enc.params.length).toEqual(196) expect(enc.node.attachment instanceof Uint8Array).toBeTruthy() }) + +test('Canonicalize ipld eip712 object', async () => { + const enc = fromEip712(testData) + const res1 = setupCanonicalizer(enc.params) + expect(res1.remainder.length).toEqual(0) + + const testRemainder = new Uint8Array([1, 2, 3]) + const res2 = setupCanonicalizer( + uint8arrays.concat([enc.params, testRemainder]) + ) + expect(res2.remainder.length).toEqual(testRemainder.length) + expect(res2.remainder).toEqual(testRemainder) +}) From c2756adc6ac3f28cceeabc5a8045a93788f40b8e Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Mon, 20 Nov 2023 23:46:23 +0300 Subject: [PATCH 011/110] eip712 fixes --- packages/varsig/src/encoding/eip712.ts | 26 ++++++++------------ packages/varsig/test/encoding/eip712.test.ts | 2 ++ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index 2e10d223..e33e4b66 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -1,7 +1,6 @@ import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' -type IpldEip712Type = Record interface Eip712Domain { name: string, @@ -10,14 +9,7 @@ interface Eip712Domain { verifyingContract: string } -interface IpldEip712Meta { - types: Record - primary: string - domain: Eip712Domain -} - -type IpldEip712Message = Record - +type IpldNode = Record interface Eip712TypeField { name: string, @@ -42,6 +34,7 @@ type Canonicalizer = (node: any) => Uint8Array; interface CanonicalizerSetup { remainder: Uint8Array, + params: any, canonicalizer: Canonicalizer } @@ -50,19 +43,20 @@ export const CODEC = 0xe712 // TODO encode as varint export function setupCanonicalizer(varsigReminder: Uint8Array): CanonicalizerSetup { const [metadataLength, read] = varintes.decode(varsigReminder) const metadataBytes = varsigReminder.subarray(read, read + metadataLength) - const [compTypes, primaryType, domain] = JSON.parse(uint8arrays.toString(metadataBytes)) + const [types, primaryType, domain] = JSON.parse(uint8arrays.toString(metadataBytes)) const metadata = { - types: decompressTypes(compTypes), - primary: primaryType, + types: decompressTypes(types), + primaryType, domain: decompressDomain(domain) } return { remainder: varsigReminder.subarray(read + metadataLength), + params: metadata, canonicalizer: parameterizeCanonicalizer(metadata) } } -function parameterizeCanonicalizer({ types, primary, domain }: Eip712): Canonicalizer { +function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Canonicalizer { return (node) => { // TODO } @@ -78,7 +72,7 @@ export function fromEip712({ types, domain, primaryType, message }: Eip712): ({ } } -function messageToIpld(message: Record, types: Eip712Types, selected: string): IpldEip712Message { +function messageToIpld(message: Record, types: Eip712Types, selected: string): IpldNode { const node = {} for (const [key, value] of Object.entries(message)) { const type = types[selected].find(({ name }) => name === key).type @@ -124,7 +118,7 @@ function compressTypes(types: Eip712Types): CompressedTypes { type // TODO make this more resilient .replace('uint', 'u').replace('int', 'i') .replace('bytes', 'b').replace('string', 's') - .replace('address', 'a').replace('bool', 'b') + .replace('address', 'a').replace('bool', 'o') ]) } return compressed @@ -133,7 +127,7 @@ function compressTypes(types: Eip712Types): CompressedTypes { const FULL_TYPES = { 'u': 'uint', 'i': 'int', 'b': 'bytes', 's': 'string', - 'a': 'address', 'b': 'bool' + 'a': 'address', 'o': 'bool' } function decompressTypes(compressed: CompressedTypes): Eip712Types { diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts index eb219597..266a06b9 100644 --- a/packages/varsig/test/encoding/eip712.test.ts +++ b/packages/varsig/test/encoding/eip712.test.ts @@ -82,6 +82,7 @@ test('Canonicalize ipld eip712 object', async () => { const enc = fromEip712(testData) const res1 = setupCanonicalizer(enc.params) expect(res1.remainder.length).toEqual(0) + expect(res1.params).toEqual({ types: testData.types, primaryType: testData.primaryType, domain: testData.domain }) const testRemainder = new Uint8Array([1, 2, 3]) const res2 = setupCanonicalizer( @@ -89,4 +90,5 @@ test('Canonicalize ipld eip712 object', async () => { ) expect(res2.remainder.length).toEqual(testRemainder.length) expect(res2.remainder).toEqual(testRemainder) + expect(res2.params).toEqual({ types: testData.types, primaryType: testData.primaryType, domain: testData.domain }) }) From 979f2301a5005acef46d132a585c94f55abdee0a Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 11:46:45 +0300 Subject: [PATCH 012/110] wip --- packages/varsig/src/encoding/eip712.ts | 66 +++++++++++++++----------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index e33e4b66..dddede07 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -1,18 +1,17 @@ import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' - interface Eip712Domain { - name: string, - version: string, - chainId: number, + name: string + version: string + chainId: number verifyingContract: string } type IpldNode = Record interface Eip712TypeField { - name: string, + name: string type: string } @@ -26,15 +25,14 @@ interface Eip712 { } type CompressedType = [string, string][] -type CompressedTypes = Record +type CompressedTypes = Record type CompressedDomain = [string, string, number, string] - -type Canonicalizer = (node: any) => Uint8Array; +type Canonicalizer = (node: any) => Uint8Array interface CanonicalizerSetup { - remainder: Uint8Array, - params: any, + remainder: Uint8Array + params: any canonicalizer: Canonicalizer } @@ -47,12 +45,12 @@ export function setupCanonicalizer(varsigReminder: Uint8Array): CanonicalizerSet const metadata = { types: decompressTypes(types), primaryType, - domain: decompressDomain(domain) + domain: decompressDomain(domain), } return { remainder: varsigReminder.subarray(read + metadataLength), params: metadata, - canonicalizer: parameterizeCanonicalizer(metadata) + canonicalizer: parameterizeCanonicalizer(metadata), } } @@ -62,24 +60,31 @@ function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Cano } } -export function fromEip712({ types, domain, primaryType, message }: Eip712): ({ node: IpldEip712, params: Uint8Array }) { - const metadata = JSON.stringify([ compressTypes(types), primaryType, compressDomain(domain) ]) +export function fromEip712({ types, domain, primaryType, message }: Eip712): { + node: IpldEip712 + params: Uint8Array +} { + const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] return { node: messageToIpld(message, types, primaryType), - params: uint8arrays.concat([metadataLength, metadataBytes]) + params: uint8arrays.concat([metadataLength, metadataBytes]), } } -function messageToIpld(message: Record, types: Eip712Types, selected: string): IpldNode { +function messageToIpld( + message: Record, + types: Eip712Types, + selected: string +): IpldNode { const node = {} for (const [key, value] of Object.entries(message)) { const type = types[selected].find(({ name }) => name === key).type if (!type) throw new Error(`Type for ${key} not found`) if (type.startsWith('bytes')) { node[key] = uint8arrays.fromString(value.slice(2), 'base16') - // check if first characther is upper case + // check if first characther is upper case } else if (type[0] === type[0].toUpperCase()) { node[key] = messageToIpld(value, types, type) } else { @@ -94,10 +99,10 @@ const EIP712_DOMAIN = [ { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, -]; +] function compressDomain(domain: Eip712Domain): CompressedDomain { - return [ domain.name, domain.version, domain.chainId, domain.verifyingContract ] + return [domain.name, domain.version, domain.chainId, domain.verifyingContract] } function decompressDomain(domain: CompressedDomain): Eip712Domain { @@ -105,7 +110,7 @@ function decompressDomain(domain: CompressedDomain): Eip712Domain { name: domain[0], version: domain[1], chainId: domain[2], - verifyingContract: domain[3] + verifyingContract: domain[3], } } @@ -116,18 +121,24 @@ function compressTypes(types: Eip712Types): CompressedTypes { compressed[key] = value.map(({ name, type }) => [ name, type // TODO make this more resilient - .replace('uint', 'u').replace('int', 'i') - .replace('bytes', 'b').replace('string', 's') - .replace('address', 'a').replace('bool', 'o') + .replace('uint', 'u') + .replace('int', 'i') + .replace('bytes', 'b') + .replace('string', 's') + .replace('address', 'a') + .replace('bool', 'o'), ]) } return compressed } const FULL_TYPES = { - 'u': 'uint', 'i': 'int', - 'b': 'bytes', 's': 'string', - 'a': 'address', 'o': 'bool' + u: 'uint', + i: 'int', + b: 'bytes', + s: 'string', + a: 'address', + o: 'bool', } function decompressTypes(compressed: CompressedTypes): Eip712Types { @@ -135,9 +146,8 @@ function decompressTypes(compressed: CompressedTypes): Eip712Types { for (const [key, value] of Object.entries(compressed)) { types[key] = value.map(([name, type]) => ({ name, - type: FULL_TYPES[type] || type + type: FULL_TYPES[type] || type, })) } return types } - From 9800f29e1803e28e96e940f4ab88e1a5ed0b32f0 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Tue, 21 Nov 2023 12:54:40 +0300 Subject: [PATCH 013/110] Full eip712 impl --- packages/varsig/src/encoding/eip712.ts | 53 +++++-- packages/varsig/test/encoding/eip712.test.ts | 152 ++++++++++--------- pnpm-lock.yaml | 88 ++++++++++- 3 files changed, 208 insertions(+), 85 deletions(-) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index dddede07..e656e9ed 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -1,5 +1,6 @@ import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' +import { hashTypedData } from 'viem' interface Eip712Domain { name: string @@ -28,20 +29,36 @@ type CompressedType = [string, string][] type CompressedTypes = Record type CompressedDomain = [string, string, number, string] -type Canonicalizer = (node: any) => Uint8Array +interface CanonicalizerResult { + digest: Uint8Array + decoded: any +} +type Canonicalize = (node: IpldNode) => CanonicalizerResult interface CanonicalizerSetup { remainder: Uint8Array - params: any - canonicalizer: Canonicalizer + canonicalize: Canonicalize } +const SUPPORTED_KEY_TYPES = [ + 0xe7, // secp256k1 + 0x1271 // eip1271 contract signature +] +const SUPPORTED_HASH_TYPE = 0x1b // keccak256 + export const CODEC = 0xe712 // TODO encode as varint -export function setupCanonicalizer(varsigReminder: Uint8Array): CanonicalizerSetup { +export function setupCanonicalizer( + varsigReminder: Uint8Array, + hasher: (data: Uint8Array) => Uint8Array, + hashType: number, + keyType: number +): CanonicalizerSetup { const [metadataLength, read] = varintes.decode(varsigReminder) const metadataBytes = varsigReminder.subarray(read, read + metadataLength) const [types, primaryType, domain] = JSON.parse(uint8arrays.toString(metadataBytes)) + if (SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) + if (hashType === SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashType}`) const metadata = { types: decompressTypes(types), primaryType, @@ -49,15 +66,33 @@ export function setupCanonicalizer(varsigReminder: Uint8Array): CanonicalizerSet } return { remainder: varsigReminder.subarray(read + metadataLength), - params: metadata, - canonicalizer: parameterizeCanonicalizer(metadata), + canonicalize: parameterizeCanonicalizer(metadata), } } -function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Canonicalizer { - return (node) => { - // TODO +function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Canonicalize { + return (node: IpldNode) => { + const message = ipldNodeToMessage(node) + const hexHash = hashTypedData({ types, primaryType, domain, message }) + return { + digest: uint8arrays.fromString(hexHash.slice(2), 'base16'), + decoded: { types, primaryType, domain, message }, + } + } +} + +function ipldNodeToMessage(node: IpldNode): Record { + const message = {} + for (const [key, value] of Object.entries(node)) { + if (value instanceof Uint8Array) { + message[key] = `0x${uint8arrays.toString(value, 'base16')}` + } else if (typeof value === 'object') { + message[key] = ipldNodeToMessage(value) + } else { + message[key] = value + } } + return message } export function fromEip712({ types, domain, primaryType, message }: Eip712): { diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts index 266a06b9..17200d53 100644 --- a/packages/varsig/test/encoding/eip712.test.ts +++ b/packages/varsig/test/encoding/eip712.test.ts @@ -2,93 +2,101 @@ import { fromEip712, setupCanonicalizer } from '../../src/encoding/eip712.ts' import * as uint8arrays from 'uint8arrays' const testData = { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Person": [ - { - "name": "name", - "type": "string" - }, - { - "name": "wallet", - "type": "address" - } - ], - "Mail": [ - { - "name": "from", - "type": "Person" - }, - { - "name": "to", - "type": "Person" - }, - { - "name": "contents", - "type": "string" - }, - { - "name": "attachment", - "type": "bytes" - } - ] + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + { + "name": "version", + "type": "string" }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!", - "attachment": "0xababababababababababa83459873459873459873498575986734359" + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + }, + { + "name": "attachment", + "type": "bytes" } - } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!", + "attachment": "0xababababababababababa83459873459873459873498575986734359" + } +} +const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') + test('Encode eip712 message', async () => { const enc = fromEip712(testData) expect(enc.params.length).toEqual(196) expect(enc.node.attachment instanceof Uint8Array).toBeTruthy() + console.log(enc.node) }) test('Canonicalize ipld eip712 object', async () => { const enc = fromEip712(testData) - const res1 = setupCanonicalizer(enc.params) - expect(res1.remainder.length).toEqual(0) - expect(res1.params).toEqual({ types: testData.types, primaryType: testData.primaryType, domain: testData.domain }) + const can1 = setupCanonicalizer(enc.params) + expect(can1.remainder.length).toEqual(0) + const res1 = can1.canonicalize(enc.node) + expect(res1.decoded).toEqual(testData) + expect(res1.digest).toEqual(expectedHash) + // extra remainder should not affect parsing const testRemainder = new Uint8Array([1, 2, 3]) - const res2 = setupCanonicalizer( + const can2 = setupCanonicalizer( uint8arrays.concat([enc.params, testRemainder]) ) - expect(res2.remainder.length).toEqual(testRemainder.length) - expect(res2.remainder).toEqual(testRemainder) - expect(res2.params).toEqual({ types: testData.types, primaryType: testData.primaryType, domain: testData.domain }) + expect(can2.remainder.length).toEqual(testRemainder.length) + expect(can2.remainder).toEqual(testRemainder) + const res2 = can2.canonicalize(enc.node) + expect(res2.decoded).toEqual(testData) + expect(res2.digest).toEqual(expectedHash) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44a3a705..3c78c53c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -594,6 +594,9 @@ importers: varintes: specifier: ^2.0.5 version: 2.0.5 + viem: + specifier: ^1.19.5 + version: 1.19.5(typescript@5.0.4) devDependencies: '@stablelib/random': specifier: ^1.0.2 @@ -672,6 +675,10 @@ packages: xml2js: 0.5.0 dev: false + /@adraffy/ens-normalize@1.10.0: + resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==} + dev: false + /@algolia/autocomplete-core@1.9.2(@algolia/client-search@4.17.2)(algoliasearch@4.17.2)(search-insights@2.6.0): resolution: {integrity: sha512-hkG80c9kx9ClVAEcUJbTd2ziVC713x9Bji9Ty4XJfKXlxlsx3iXsoNhAwfeR4ulzIUg7OE5gez0UU1zVDdG7kg==} dependencies: @@ -2945,7 +2952,7 @@ packages: engines: {node: '>=14.14'} dependencies: '@didtools/cacao': 2.0.0 - '@noble/curves': 1.1.0 + '@noble/curves': 1.2.0 '@stablelib/random': 1.0.2 caip: 1.1.0 uint8arrays: 4.0.3 @@ -2969,8 +2976,8 @@ packages: engines: {node: '>=14.14'} dependencies: '@didtools/cacao': 2.0.0 - '@noble/curves': 1.1.0 - '@noble/hashes': 1.3.1 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 '@stablelib/random': 1.0.2 caip: 1.1.0 uint8arrays: 4.0.3 @@ -5665,6 +5672,11 @@ packages: dependencies: '@noble/hashes': 1.3.1 + /@noble/curves@1.2.0: + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + dependencies: + '@noble/hashes': 1.3.2 + /@noble/ed25519@1.7.3: resolution: {integrity: sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==} dev: false @@ -5680,6 +5692,10 @@ packages: resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==} engines: {node: '>= 16'} + /@noble/hashes@1.3.2: + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + /@noble/secp256k1@1.7.1: resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} @@ -6036,12 +6052,31 @@ packages: /@scure/base@1.1.1: resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==} + /@scure/base@1.1.3: + resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==} + dev: false + + /@scure/bip32@1.3.2: + resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==} + dependencies: + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/base': 1.1.3 + dev: false + /@scure/bip39@1.1.0: resolution: {integrity: sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==} dependencies: '@noble/hashes': 1.1.5 '@scure/base': 1.1.1 + /@scure/bip39@1.2.1: + resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} + dependencies: + '@noble/hashes': 1.3.2 + '@scure/base': 1.1.1 + dev: false + /@sideway/address@4.1.4: resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} dependencies: @@ -7532,6 +7567,20 @@ packages: requiresBuild: true dev: false + /abitype@0.9.8(typescript@5.0.4): + resolution: {integrity: sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.19.1 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + dependencies: + typescript: 5.0.4 + dev: false + /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -12955,6 +13004,14 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + /isows@1.0.3(ws@8.13.0): + resolution: {integrity: sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==} + peerDependencies: + ws: '*' + dependencies: + ws: 8.13.0 + dev: false + /istanbul-lib-coverage@3.2.0: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} @@ -14799,7 +14856,7 @@ packages: /multihashes-sync@1.1.1: resolution: {integrity: sha512-6KCdl3Em5m8q5+06npekkaDPS1RLXV/0FRo24Q04LvRpJp50wPhQ3l5NCXaQU1TBS/v6hTRMwNtVfXqCMSEO9A==} dependencies: - '@noble/hashes': 1.3.1 + '@noble/hashes': 1.3.2 multiformats: 11.0.2 dev: false @@ -18848,6 +18905,29 @@ packages: vfile-message: 2.0.4 dev: false + /viem@1.19.5(typescript@5.0.4): + resolution: {integrity: sha512-lcWP9ErivUKVRpnDgI6mly27iWibJN1jZW2yUGuwU2CjfVHs7DJCyA5ityL+n/B+Oss4bllzQkWtJiBAHqaX2g==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/bip32': 1.3.2 + '@scure/bip39': 1.2.1 + abitype: 0.9.8(typescript@5.0.4) + isows: 1.0.3(ws@8.13.0) + typescript: 5.0.4 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + dev: false + /vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} dev: true From d7c53fd34ed6f817ca6a355d46ac3a8048d59080 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 14:21:09 +0300 Subject: [PATCH 014/110] wip --- packages/varsig/src/at0.ts | 31 ++++++ packages/varsig/test/parse-eip191.test.ts | 129 ++++++++++++++++++++++ packages/varsig/test/varsig.test.ts | 1 + pnpm-lock.yaml | 14 +-- 4 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 packages/varsig/src/at0.ts create mode 100644 packages/varsig/test/parse-eip191.test.ts diff --git a/packages/varsig/src/at0.ts b/packages/varsig/src/at0.ts new file mode 100644 index 00000000..21252830 --- /dev/null +++ b/packages/varsig/src/at0.ts @@ -0,0 +1,31 @@ +export enum SIGNING { + RSA = 0x1205, + SECP256K1 = 0xe7, +} + +export enum HASHING { + SHA2_256 = 0x12, + SHA2_512 = 0x13, + KECCAK256 = 0x1b, +} + +export type SigningSECP256K1 = { + kind: SIGNING.SECP256K1 + recoveryBit: undefined | 27 | 28 +} + +export enum CANONICALIZATION { + EIP712 = 0xe712, + EIP191 = 0xe191, +} + +export type CanonicalizationEIP191 = { + kind: CANONICALIZATION.EIP191 +} + +export type Varsig = { + signing: TSigniing + hashing: HASHING + canonicalization: TCanonicalization + signature: Uint8Array +} diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts new file mode 100644 index 00000000..7907027e --- /dev/null +++ b/packages/varsig/test/parse-eip191.test.ts @@ -0,0 +1,129 @@ +import { test } from '@jest/globals' +import * as varintes from 'varintes' +import { CANONICALIZATION, HASHING, SIGNING } from '../src/at0.js' +import { secp256k1 } from '@noble/curves/secp256k1' +import { sha256 } from '@noble/hashes/sha256' +import { keccak_256 } from '@noble/hashes/sha3' +import * as uint8arrays from 'uint8arrays' +import { privateKeyToAccount } from 'viem/accounts' +import { validate } from 'typedoc/dist/lib/utils/validation' + +class UnreacheableCaseError extends Error { + constructor(variant: never) { + super(`Unreacheable case: ${String(variant)}`) + } +} + +function decode(input: Uint8Array) { + const [sigil, sigilRead] = varintes.decode(input) + if (sigil !== 0x34) throw new Error(`Not a varsig`) + const sigilRemainder = input.subarray(sigilRead) + const [signingVarint, signingVarintRead] = varintes.decode(sigilRemainder) + const signingVarintRemainder = sigilRemainder.subarray(signingVarintRead) + const signingSigil = signingVarint as SIGNING + switch (signingSigil) { + case SIGNING.SECP256K1: { + const [recoveryBit, recoveryBitRead] = varintes.decode(signingVarintRemainder) + if (!(recoveryBit === 27 || recoveryBit === 28)) { + throw new Error(`Wrong recovery bit`) + } + const recoveryBitRemainder = signingVarintRemainder.subarray(recoveryBitRead) + const [hashingVarint, hashingVarintRead] = varintes.decode(recoveryBitRemainder) + const hashingVarintRemainder = recoveryBitRemainder.subarray(hashingVarintRead) + const hashingSigil = hashingVarint as HASHING + switch (hashingSigil) { + case HASHING.KECCAK256: { + const [canonicalizationKind, canonicalizationKindRead] = + varintes.decode(hashingVarintRemainder) + const canonicalizationSigil = canonicalizationKind as CANONICALIZATION + switch (canonicalizationSigil) { + case CANONICALIZATION.EIP191: { + if (recoveryBit > 0) { + const canonicalizationRemainder = + hashingVarintRemainder.subarray(canonicalizationKindRead) + const signature = canonicalizationRemainder.subarray(0, 65) + const signingInput = (message: Uint8Array) => { + const m = uint8arrays.toString(message) + return keccak_256( + uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) + ) + } + // address + return { + signing: SIGNING.SECP256K1, + recoveryBit: recoveryBit, + signature: signature, + signingInput: signingInput, + } + } else { + // public key + const signature = hashingVarintRemainder.subarray(0, 64) + const signingInput = (message: Uint8Array) => { + const m = uint8arrays.toString(message) + return keccak_256( + uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) + ) + } + // address + return { + signing: SIGNING.SECP256K1, + signature: signature, + signingInput: signingInput, + } + } + } + case CANONICALIZATION.EIP712: + throw new Error(`Not implemented: 712`) + default: + throw new UnreacheableCaseError(canonicalizationSigil) + } + } + case HASHING.SHA2_256: + throw new Error(`Not implemented: HASHING.SHA2_256`) + case HASHING.SHA2_512: + throw new Error(`Not implemented: HASHING.SHA2_512`) + default: + throw new UnreacheableCaseError(hashingSigil) + } + } + case SIGNING.RSA: + throw new Error(`Not implemented: HASHING.RSA`) + default: + throw new UnreacheableCaseError(signingSigil) + } +} + +function eip191canonicalization(message: string) { + return uint8arrays.fromString(message) +} + +function hex(...numbers: Array): Uint8Array { + return new Uint8Array(numbers) +} + +test('validate eip191', async () => { + const account = privateKeyToAccount( + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + ) + console.log('pub', account.publicKey) + const stringSignature = await account.signMessage({ message: 'Hello World' }) + const signatureBytes = uint8arrays.fromString( + stringSignature.toLowerCase().replace(/^0x/, ''), + 'hex' + ) + const varsig = uint8arrays.concat([ + hex(0x34), + varintes.encode(0xe7)[0], + signatureBytes.subarray(64), + varintes.encode(0x1b)[0], + varintes.encode(CANONICALIZATION.EIP191)[0], + signatureBytes.subarray(0, 64), + ]) + const a = decode(varsig) + const input = a.signingInput(eip191canonicalization('Hello World')) + let sig = secp256k1.Signature.fromCompact(a.signature) + if (a.recoveryBit) { + sig = sig.addRecoveryBit(a.recoveryBit - 27) + } + console.log(sig.recoverPublicKey(input).toHex(false)) +}) diff --git a/packages/varsig/test/varsig.test.ts b/packages/varsig/test/varsig.test.ts index cacfda99..26769983 100644 --- a/packages/varsig/test/varsig.test.ts +++ b/packages/varsig/test/varsig.test.ts @@ -25,6 +25,7 @@ test('rsa', async () => { key.privateKey, new Uint8Array([1, 2, 3]) ) + console.log('verif', await crypto.subtle.verify({ name: 'RSASSA-PKCS1-v1_5'}, key.publicKey, a, new Uint8Array([1, 2, 3]))) const signatureBytes = new Uint8Array(a) const b = toIPLD({ payload: {}, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c78c53c..9e2613c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -6074,7 +6074,7 @@ packages: resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} dependencies: '@noble/hashes': 1.3.2 - '@scure/base': 1.1.1 + '@scure/base': 1.1.3 dev: false /@sideway/address@4.1.4: @@ -8340,7 +8340,7 @@ packages: /bip39@3.1.0: resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} dependencies: - '@noble/hashes': 1.3.1 + '@noble/hashes': 1.3.2 /bl@5.1.0: resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} @@ -8619,7 +8619,7 @@ packages: resolution: {integrity: sha512-rpwfAcS/CMqo0oCqDf3r9eeLgScRE3l/xHDCXhM3UyrfvIn7PrLq63uHh7yYbv8NzaZn5MVsVhIRpQ+5GZ5HyA==} engines: {node: '>=8'} dependencies: - '@noble/hashes': 1.3.1 + '@noble/hashes': 1.3.2 base-x: 4.0.0 /cacache@15.3.0: @@ -9856,8 +9856,8 @@ packages: /did-jwt@7.2.0: resolution: {integrity: sha512-VLtK4eJKm7eRB4SmQrGJwhqlmxKGqGfoNjc711Guiw6aVIryziYzBj01bDHw1rJO9bJQ53Q0CWlXAVpgmOa7KQ==} dependencies: - '@noble/curves': 1.1.0 - '@noble/hashes': 1.3.1 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 '@stablelib/xchacha20poly1305': 1.0.1 bech32: 2.0.0 canonicalize: 2.0.0 @@ -13920,7 +13920,7 @@ packages: /jsontokens@4.0.1: resolution: {integrity: sha512-+MO415LEN6M+3FGsRz4wU20g7N2JA+2j9d9+pGaNJHviG4L8N0qzavGyENw6fJqsq9CcrHOIL6iWX5yeTZ86+Q==} dependencies: - '@noble/hashes': 1.3.1 + '@noble/hashes': 1.3.2 '@noble/secp256k1': 1.7.1 base64-js: 1.5.1 From 27fcc103fe023dde4413db3e977ac889cc1a037f Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:22:19 +0300 Subject: [PATCH 015/110] it builds --- packages/varsig/package.json | 3 +- packages/varsig/src/encoding/eip712.ts | 19 +++- packages/varsig/src/encoding/jws.ts | 86 +++++++-------- packages/varsig/test/parse-eip191.test.ts | 125 +++++++++++++++++++++- pnpm-lock.yaml | 6 +- 5 files changed, 186 insertions(+), 53 deletions(-) diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 6d9207d8..a31f5619 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -54,6 +54,7 @@ "key-did-resolver": "workspace:^", "multiformats": "^11.0.2", "uint8arrays": "^4.0.3", - "varintes": "^2.0.5" + "varintes": "^2.0.5", + "viem": "^1.19.5" } } diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index e656e9ed..0fed6e37 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -16,7 +16,7 @@ interface Eip712TypeField { type: string } -type Eip712Types = Record +type Eip712Types = Record> interface Eip712 { types: Eip712Types @@ -42,7 +42,7 @@ interface CanonicalizerSetup { const SUPPORTED_KEY_TYPES = [ 0xe7, // secp256k1 - 0x1271 // eip1271 contract signature + 0x1271, // eip1271 contract signature ] const SUPPORTED_HASH_TYPE = 0x1b // keccak256 @@ -50,6 +50,7 @@ export const CODEC = 0xe712 // TODO encode as varint export function setupCanonicalizer( varsigReminder: Uint8Array, + // @ts-ignore hasher: (data: Uint8Array) => Uint8Array, hashType: number, keyType: number @@ -73,6 +74,7 @@ export function setupCanonicalizer( function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Canonicalize { return (node: IpldNode) => { const message = ipldNodeToMessage(node) + // @ts-ignore const hexHash = hashTypedData({ types, primaryType, domain, message }) return { digest: uint8arrays.fromString(hexHash.slice(2), 'base16'), @@ -85,16 +87,21 @@ function ipldNodeToMessage(node: IpldNode): Record { const message = {} for (const [key, value] of Object.entries(node)) { if (value instanceof Uint8Array) { + // @ts-ignore message[key] = `0x${uint8arrays.toString(value, 'base16')}` } else if (typeof value === 'object') { + // @ts-ignore message[key] = ipldNodeToMessage(value) } else { + // @ts-ignore message[key] = value } } return message } +type IpldEip712 = any + export function fromEip712({ types, domain, primaryType, message }: Eip712): { node: IpldEip712 params: Uint8Array @@ -103,6 +110,7 @@ export function fromEip712({ types, domain, primaryType, message }: Eip712): { const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] return { + // @ts-ignore node: messageToIpld(message, types, primaryType), params: uint8arrays.concat([metadataLength, metadataBytes]), } @@ -115,14 +123,18 @@ function messageToIpld( ): IpldNode { const node = {} for (const [key, value] of Object.entries(message)) { + // @ts-ignore const type = types[selected].find(({ name }) => name === key).type if (!type) throw new Error(`Type for ${key} not found`) if (type.startsWith('bytes')) { + // @ts-ignore node[key] = uint8arrays.fromString(value.slice(2), 'base16') // check if first characther is upper case } else if (type[0] === type[0].toUpperCase()) { + // @ts-ignore node[key] = messageToIpld(value, types, type) } else { + // @ts-ignore node[key] = value } } @@ -153,6 +165,7 @@ function compressTypes(types: Eip712Types): CompressedTypes { const compressed = {} for (const [key, value] of Object.entries(types)) { if (key === 'EIP712Domain') continue + // @ts-ignore compressed[key] = value.map(({ name, type }) => [ name, type // TODO make this more resilient @@ -179,8 +192,10 @@ const FULL_TYPES = { function decompressTypes(compressed: CompressedTypes): Eip712Types { const types = { EIP712Domain: EIP712_DOMAIN } for (const [key, value] of Object.entries(compressed)) { + // @ts-ignore types[key] = value.map(([name, type]) => ({ name, + // @ts-ignore type: FULL_TYPES[type] || type, })) } diff --git a/packages/varsig/src/encoding/jws.ts b/packages/varsig/src/encoding/jws.ts index a94363ab..184f1733 100644 --- a/packages/varsig/src/encoding/jws.ts +++ b/packages/varsig/src/encoding/jws.ts @@ -1,43 +1,43 @@ -import type { GeneralJWS } from 'dids' -import * as uint8arrays from 'uint8arrays' -import { toBytes } from '../bytes.js' -import { ENCODING, HASHING, SIGNING } from '../varsig.js' - -export function encode(jws: GeneralJWS) { - const signature0 = jws.signatures[0] - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const protectedHeader = JSON.parse( - uint8arrays.toString(uint8arrays.fromString(signature0.protected, 'base64url')) - ) - const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(jws.payload, 'base64url'))) - const signature = uint8arrays.fromString(signature0.signature, 'base64url') - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - switch (protectedHeader.alg) { - case 'EdDSA': { - const sig = toBytes({ - encoding: ENCODING.JWT, - hashing: HASHING.SHA2_256, - signing: SIGNING.ED25519, - signature: signature, - }) - delete protectedHeader.typ - delete protectedHeader.alg - return { - _header: protectedHeader, - ...payload, - _signature: sig, - } - } - case 'ES256': { - const sig = toBytes({ - encoding: ENCODING.JWT, - hashing: HASHING.SHA2_256, - signing: SIGNING.RSA - }) - } - } - return { - _header: {}, - _sig: {}, - } -} +// import type { GeneralJWS } from 'dids' +// import * as uint8arrays from 'uint8arrays' +// import { toBytes } from '../bytes.js' +// import { ENCODING, HASHING, SIGNING } from '../varsig.js' +// +// export function encode(jws: GeneralJWS) { +// const signature0 = jws.signatures[0] +// // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +// const protectedHeader = JSON.parse( +// uint8arrays.toString(uint8arrays.fromString(signature0.protected, 'base64url')) +// ) +// const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(jws.payload, 'base64url'))) +// const signature = uint8arrays.fromString(signature0.signature, 'base64url') +// // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +// switch (protectedHeader.alg) { +// case 'EdDSA': { +// const sig = toBytes({ +// encoding: ENCODING.JWT, +// hashing: HASHING.SHA2_256, +// signing: SIGNING.ED25519, +// signature: signature, +// }) +// delete protectedHeader.typ +// delete protectedHeader.alg +// return { +// _header: protectedHeader, +// ...payload, +// _signature: sig, +// } +// } +// case 'ES256': { +// const sig = toBytes({ +// encoding: ENCODING.JWT, +// hashing: HASHING.SHA2_256, +// signing: SIGNING.RSA +// }) +// } +// } +// return { +// _header: {}, +// _sig: {}, +// } +// } diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 7907027e..1adf6279 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -6,7 +6,7 @@ import { sha256 } from '@noble/hashes/sha256' import { keccak_256 } from '@noble/hashes/sha3' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' -import { validate } from 'typedoc/dist/lib/utils/validation' +import { Tape } from 'codeco/linear' class UnreacheableCaseError extends Error { constructor(variant: never) { @@ -14,6 +14,126 @@ class UnreacheableCaseError extends Error { } } +class BytesTape implements Tape { + position: number + + constructor(readonly input: Uint8Array) { + this.position = 0 + } + + read(n: number): Uint8Array { + const result = this.input.subarray(this.position, this.position + n) + this.position += n + return result + } + + back(n: number): void { + this.position -= n + } + + readVarint(): number { + const bytes = this.read(10) + const [n, bytesRead] = varintes.decode(bytes) + this.back(10 - bytesRead) + return n + } + + get isEOF(): boolean { + return this.position >= this.input.byteLength + } +} + +class Decoder { + #tape: BytesTape + + constructor(tape: BytesTape) { + this.#tape = tape + } + + decode() { + this.readVarsigSigil() + const signingSigil = this.readSigningSigil() + switch (signingSigil) { + case SIGNING.RSA: + throw new Error(`Not implemented: signingSigil: RSA`) + case SIGNING.SECP256K1: { + const recoveryBit = this.#tape.readVarint() + if (!(recoveryBit === 27 || recoveryBit === 28)) { + throw new Error(`Wrong recovery bit`) + } + const hashingSigil = this.#tape.readVarint() as HASHING + switch (hashingSigil) { + case HASHING.KECCAK256: { + const canonicalizationSigil = this.#tape.readVarint() as CANONICALIZATION + switch (canonicalizationSigil) { + case CANONICALIZATION.EIP191: { + if (recoveryBit > 0) { + const signature = this.#tape.read(65) + const signingInput = (message: Uint8Array) => { + const m = uint8arrays.toString(message) + return keccak_256( + uint8arrays.fromString( + `\x19Ethereum Signed Message:\n` + String(m.length) + m + ) + ) + } + // address + return { + signing: SIGNING.SECP256K1, + recoveryBit: recoveryBit, + signature: signature, + signingInput: signingInput, + } + } else { + // public key + const signature = this.#tape.read(64) + const signingInput = (message: Uint8Array) => { + const m = uint8arrays.toString(message) + return keccak_256( + uint8arrays.fromString( + `\x19Ethereum Signed Message:\n` + String(m.length) + m + ) + ) + } + // address + return { + signing: SIGNING.SECP256K1, + signature: signature, + signingInput: signingInput, + } + } + } + case CANONICALIZATION.EIP712: + throw new Error(`Not implemented: 712`) + default: + throw new UnreacheableCaseError(canonicalizationSigil) + } + } + case HASHING.SHA2_256: + throw new Error(`Not implemented: HASHING.SHA2_256`) + case HASHING.SHA2_512: + throw new Error(`Not implemented: HASHING.SHA2_512`) + default: + throw new UnreacheableCaseError(hashingSigil) + } + } + default: + throw new UnreacheableCaseError(signingSigil) + } + } + + readVarsigSigil() { + const sigil = this.#tape.readVarint() + if (sigil !== 0x34) throw new Error(`Not a varsig`) + return sigil + } + + readSigningSigil() { + const sigil = this.#tape.readVarint() + return sigil as SIGNING + } +} + function decode(input: Uint8Array) { const [sigil, sigilRead] = varintes.decode(input) if (sigil !== 0x34) throw new Error(`Not a varsig`) @@ -119,7 +239,8 @@ test('validate eip191', async () => { varintes.encode(CANONICALIZATION.EIP191)[0], signatureBytes.subarray(0, 64), ]) - const a = decode(varsig) + // const a = decode(varsig) + const a = new Decoder(new BytesTape(varsig)).decode() const input = a.signingInput(eip191canonicalization('Hello World')) let sig = secp256k1.Signature.fromCompact(a.signature) if (a.recoveryBit) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e2613c9..40b403a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6049,12 +6049,8 @@ packages: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} dev: false - /@scure/base@1.1.1: - resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==} - /@scure/base@1.1.3: resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==} - dev: false /@scure/bip32@1.3.2: resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==} @@ -6068,7 +6064,7 @@ packages: resolution: {integrity: sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==} dependencies: '@noble/hashes': 1.1.5 - '@scure/base': 1.1.1 + '@scure/base': 1.1.3 /@scure/bip39@1.2.1: resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} From 404f97a635c50da2c8466e3e245ac4e46246b6b3 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:23:37 +0300 Subject: [PATCH 016/110] less stuff --- packages/varsig/package.json | 2 + packages/varsig/test/parse-eip191.test.ts | 79 ----------------------- pnpm-lock.yaml | 6 ++ 3 files changed, 8 insertions(+), 79 deletions(-) diff --git a/packages/varsig/package.json b/packages/varsig/package.json index a31f5619..86c64768 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -48,6 +48,8 @@ "@stablelib/random": "^1.0.2" }, "dependencies": { + "@noble/curves": "^1.2.0", + "@noble/hashes": "^1.3.2", "@types/node": "^20.2.3", "codeco": "^1.1.0", "key-did-provider-ed25519": "workspace:^", diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 1adf6279..f22e5c25 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -134,85 +134,6 @@ class Decoder { } } -function decode(input: Uint8Array) { - const [sigil, sigilRead] = varintes.decode(input) - if (sigil !== 0x34) throw new Error(`Not a varsig`) - const sigilRemainder = input.subarray(sigilRead) - const [signingVarint, signingVarintRead] = varintes.decode(sigilRemainder) - const signingVarintRemainder = sigilRemainder.subarray(signingVarintRead) - const signingSigil = signingVarint as SIGNING - switch (signingSigil) { - case SIGNING.SECP256K1: { - const [recoveryBit, recoveryBitRead] = varintes.decode(signingVarintRemainder) - if (!(recoveryBit === 27 || recoveryBit === 28)) { - throw new Error(`Wrong recovery bit`) - } - const recoveryBitRemainder = signingVarintRemainder.subarray(recoveryBitRead) - const [hashingVarint, hashingVarintRead] = varintes.decode(recoveryBitRemainder) - const hashingVarintRemainder = recoveryBitRemainder.subarray(hashingVarintRead) - const hashingSigil = hashingVarint as HASHING - switch (hashingSigil) { - case HASHING.KECCAK256: { - const [canonicalizationKind, canonicalizationKindRead] = - varintes.decode(hashingVarintRemainder) - const canonicalizationSigil = canonicalizationKind as CANONICALIZATION - switch (canonicalizationSigil) { - case CANONICALIZATION.EIP191: { - if (recoveryBit > 0) { - const canonicalizationRemainder = - hashingVarintRemainder.subarray(canonicalizationKindRead) - const signature = canonicalizationRemainder.subarray(0, 65) - const signingInput = (message: Uint8Array) => { - const m = uint8arrays.toString(message) - return keccak_256( - uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) - ) - } - // address - return { - signing: SIGNING.SECP256K1, - recoveryBit: recoveryBit, - signature: signature, - signingInput: signingInput, - } - } else { - // public key - const signature = hashingVarintRemainder.subarray(0, 64) - const signingInput = (message: Uint8Array) => { - const m = uint8arrays.toString(message) - return keccak_256( - uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) - ) - } - // address - return { - signing: SIGNING.SECP256K1, - signature: signature, - signingInput: signingInput, - } - } - } - case CANONICALIZATION.EIP712: - throw new Error(`Not implemented: 712`) - default: - throw new UnreacheableCaseError(canonicalizationSigil) - } - } - case HASHING.SHA2_256: - throw new Error(`Not implemented: HASHING.SHA2_256`) - case HASHING.SHA2_512: - throw new Error(`Not implemented: HASHING.SHA2_512`) - default: - throw new UnreacheableCaseError(hashingSigil) - } - } - case SIGNING.RSA: - throw new Error(`Not implemented: HASHING.RSA`) - default: - throw new UnreacheableCaseError(signingSigil) - } -} - function eip191canonicalization(message: string) { return uint8arrays.fromString(message) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40b403a6..b1226c9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -573,6 +573,12 @@ importers: packages/varsig: dependencies: + '@noble/curves': + specifier: ^1.2.0 + version: 1.2.0 + '@noble/hashes': + specifier: ^1.3.2 + version: 1.3.2 '@types/node': specifier: ^20.2.3 version: 20.2.3 From 7e36eb4c1b1095423a490deac197f780b30c8d3f Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:25:23 +0300 Subject: [PATCH 017/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index f22e5c25..908d7423 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -27,15 +27,11 @@ class BytesTape implements Tape { return result } - back(n: number): void { - this.position -= n - } - - readVarint(): number { + readVarint(): T { const bytes = this.read(10) const [n, bytesRead] = varintes.decode(bytes) - this.back(10 - bytesRead) - return n + this.position -= 10 - bytesRead + return n as T } get isEOF(): boolean { @@ -61,10 +57,10 @@ class Decoder { if (!(recoveryBit === 27 || recoveryBit === 28)) { throw new Error(`Wrong recovery bit`) } - const hashingSigil = this.#tape.readVarint() as HASHING + const hashingSigil = this.#tape.readVarint() switch (hashingSigil) { case HASHING.KECCAK256: { - const canonicalizationSigil = this.#tape.readVarint() as CANONICALIZATION + const canonicalizationSigil = this.#tape.readVarint() switch (canonicalizationSigil) { case CANONICALIZATION.EIP191: { if (recoveryBit > 0) { From cba36a18312f43c4837611c4d45590294c47cfe1 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:30:19 +0300 Subject: [PATCH 018/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 908d7423..25a73373 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -46,6 +46,26 @@ class Decoder { this.#tape = tape } + readSigning() { + const signingSigil = this.#tape.readVarint() + switch (signingSigil) { + case SIGNING.SECP256K1: { + const recoveryBit = this.#tape.readVarint() + if (!(recoveryBit === 27 || recoveryBit === 28)) { + throw new Error(`Wrong recovery bit`) + } + return { + signing: SIGNING.SECP256K1, + recoveryBit: recoveryBit || undefined, + } + } + case SIGNING.RSA: + throw new Error(`Not implemented: signingSigil: RSA`) + default: + throw new UnreacheableCaseError(signingSigil) + } + } + decode() { this.readVarsigSigil() const signingSigil = this.readSigningSigil() From a61c677decccb53b34eb72c1f4bb6a5623c70f33 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:32:34 +0300 Subject: [PATCH 019/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 25 +++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 25a73373..ac635587 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -39,6 +39,11 @@ class BytesTape implements Tape { } } +type Signing = { + kind: SIGNING.SECP256K1 + recoveryBit: undefined | 27 | 28 +} + class Decoder { #tape: BytesTape @@ -46,7 +51,7 @@ class Decoder { this.#tape = tape } - readSigning() { + readSigning(): Signing { const signingSigil = this.#tape.readVarint() switch (signingSigil) { case SIGNING.SECP256K1: { @@ -55,7 +60,7 @@ class Decoder { throw new Error(`Wrong recovery bit`) } return { - signing: SIGNING.SECP256K1, + kind: SIGNING.SECP256K1, recoveryBit: recoveryBit || undefined, } } @@ -68,22 +73,16 @@ class Decoder { decode() { this.readVarsigSigil() - const signingSigil = this.readSigningSigil() - switch (signingSigil) { - case SIGNING.RSA: - throw new Error(`Not implemented: signingSigil: RSA`) + const signing = this.readSigning() + switch (signing.kind) { case SIGNING.SECP256K1: { - const recoveryBit = this.#tape.readVarint() - if (!(recoveryBit === 27 || recoveryBit === 28)) { - throw new Error(`Wrong recovery bit`) - } const hashingSigil = this.#tape.readVarint() switch (hashingSigil) { case HASHING.KECCAK256: { const canonicalizationSigil = this.#tape.readVarint() switch (canonicalizationSigil) { case CANONICALIZATION.EIP191: { - if (recoveryBit > 0) { + if (signing.recoveryBit) { const signature = this.#tape.read(65) const signingInput = (message: Uint8Array) => { const m = uint8arrays.toString(message) @@ -96,7 +95,7 @@ class Decoder { // address return { signing: SIGNING.SECP256K1, - recoveryBit: recoveryBit, + recoveryBit: signing.recoveryBit, signature: signature, signingInput: signingInput, } @@ -134,7 +133,7 @@ class Decoder { } } default: - throw new UnreacheableCaseError(signingSigil) + throw new UnreacheableCaseError(signing.kind) } } From 4dffe856c6ec8ff925209c4618e7b851d55d6f18 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:41:39 +0300 Subject: [PATCH 020/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 30 +++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index ac635587..55c501b2 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -44,6 +44,10 @@ type Signing = { recoveryBit: undefined | 27 | 28 } +type Hashing = { + kind: HASHING.KECCAK256 +} + class Decoder { #tape: BytesTape @@ -71,13 +75,29 @@ class Decoder { } } + readHashing(): Hashing { + const hashingSigil = this.#tape.readVarint() + switch (hashingSigil) { + case HASHING.SHA2_512: + throw new Error(`Not implemented: hashingSigil: SHA2_512`) + case HASHING.SHA2_256: + throw new Error(`Not implemented: hashingSigil: SHA2_256`) + case HASHING.KECCAK256: + return { + kind: HASHING.KECCAK256, + } + default: + throw new UnreacheableCaseError(hashingSigil) + } + } + decode() { this.readVarsigSigil() const signing = this.readSigning() + const hashing = this.readHashing() switch (signing.kind) { case SIGNING.SECP256K1: { - const hashingSigil = this.#tape.readVarint() - switch (hashingSigil) { + switch (hashing.kind) { case HASHING.KECCAK256: { const canonicalizationSigil = this.#tape.readVarint() switch (canonicalizationSigil) { @@ -124,12 +144,8 @@ class Decoder { throw new UnreacheableCaseError(canonicalizationSigil) } } - case HASHING.SHA2_256: - throw new Error(`Not implemented: HASHING.SHA2_256`) - case HASHING.SHA2_512: - throw new Error(`Not implemented: HASHING.SHA2_512`) default: - throw new UnreacheableCaseError(hashingSigil) + throw new UnreacheableCaseError(hashing.kind) } } default: From 004b0d1a0a0eed6a5fed8f97d9e95a3ef130f62d Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:48:29 +0300 Subject: [PATCH 021/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 83 ++++++++--------------- 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 55c501b2..ae00d6f9 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -91,68 +91,39 @@ class Decoder { } } - decode() { - this.readVarsigSigil() - const signing = this.readSigning() - const hashing = this.readHashing() - switch (signing.kind) { - case SIGNING.SECP256K1: { - switch (hashing.kind) { - case HASHING.KECCAK256: { - const canonicalizationSigil = this.#tape.readVarint() - switch (canonicalizationSigil) { - case CANONICALIZATION.EIP191: { - if (signing.recoveryBit) { - const signature = this.#tape.read(65) - const signingInput = (message: Uint8Array) => { - const m = uint8arrays.toString(message) - return keccak_256( - uint8arrays.fromString( - `\x19Ethereum Signed Message:\n` + String(m.length) + m - ) - ) - } - // address - return { - signing: SIGNING.SECP256K1, - recoveryBit: signing.recoveryBit, - signature: signature, - signingInput: signingInput, - } - } else { - // public key - const signature = this.#tape.read(64) - const signingInput = (message: Uint8Array) => { - const m = uint8arrays.toString(message) - return keccak_256( - uint8arrays.fromString( - `\x19Ethereum Signed Message:\n` + String(m.length) + m - ) - ) - } - // address - return { - signing: SIGNING.SECP256K1, - signature: signature, - signingInput: signingInput, - } - } - } - case CANONICALIZATION.EIP712: - throw new Error(`Not implemented: 712`) - default: - throw new UnreacheableCaseError(canonicalizationSigil) - } - } - default: - throw new UnreacheableCaseError(hashing.kind) + readCanonicalization(signing: Signing, hashing: Hashing) { + const sigil = this.#tape.readVarint() + const signingInput = (message: Uint8Array) => { + const m = uint8arrays.toString(message) + return keccak_256( + uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) + ) + } + switch (sigil) { + case CANONICALIZATION.EIP712: + throw new Error(`Not implemented: readCanonicalization: EIP712`) + case CANONICALIZATION.EIP191: { + const signatureLen = signing.recoveryBit ? 65 : 64 + const signature = this.#tape.read(signatureLen) + return { + signing: SIGNING.SECP256K1, + recoveryBit: signing.recoveryBit, + signature: signature, + signingInput: signingInput, } } default: - throw new UnreacheableCaseError(signing.kind) + throw new UnreacheableCaseError(sigil) } } + decode() { + this.readVarsigSigil() + const signing = this.readSigning() + const hashing = this.readHashing() + return this.readCanonicalization(signing, hashing) + } + readVarsigSigil() { const sigil = this.#tape.readVarint() if (sigil !== 0x34) throw new Error(`Not a varsig`) From 5861a6b0d04c2db16c9c2328ece080dd24826e10 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 15:58:26 +0300 Subject: [PATCH 022/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 50 +++++++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index ae00d6f9..48c18ea8 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -48,18 +48,18 @@ type Hashing = { kind: HASHING.KECCAK256 } -class Decoder { - #tape: BytesTape +class SigningDecoder { + constructor(private readonly tape: BytesTape) {} - constructor(tape: BytesTape) { - this.#tape = tape + static read(tape: BytesTape): Signing { + return new SigningDecoder(tape).read() } - readSigning(): Signing { - const signingSigil = this.#tape.readVarint() + read(): Signing { + const signingSigil = this.tape.readVarint() switch (signingSigil) { case SIGNING.SECP256K1: { - const recoveryBit = this.#tape.readVarint() + const recoveryBit = this.tape.readVarint() if (!(recoveryBit === 27 || recoveryBit === 28)) { throw new Error(`Wrong recovery bit`) } @@ -74,6 +74,38 @@ class Decoder { throw new UnreacheableCaseError(signingSigil) } } +} + +class HashingDecoder { + constructor(private readonly tape: BytesTape) {} + + static read(tape: BytesTape) { + return new HashingDecoder(tape).read() + } + + read(): Hashing { + const hashingSigil = this.tape.readVarint() + switch (hashingSigil) { + case HASHING.SHA2_512: + throw new Error(`Not implemented: hashingSigil: SHA2_512`) + case HASHING.SHA2_256: + throw new Error(`Not implemented: hashingSigil: SHA2_256`) + case HASHING.KECCAK256: + return { + kind: HASHING.KECCAK256, + } + default: + throw new UnreacheableCaseError(hashingSigil) + } + } +} + +class Decoder { + #tape: BytesTape + + constructor(tape: BytesTape) { + this.#tape = tape + } readHashing(): Hashing { const hashingSigil = this.#tape.readVarint() @@ -119,8 +151,8 @@ class Decoder { decode() { this.readVarsigSigil() - const signing = this.readSigning() - const hashing = this.readHashing() + const signing = SigningDecoder.read(this.#tape) + const hashing = HashingDecoder.read(this.#tape) return this.readCanonicalization(signing, hashing) } From 4ae43026769dbde9feb3cf21f69d6d19fa9a395b Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:00:52 +0300 Subject: [PATCH 023/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 52 +++++++++-------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 48c18ea8..6717d8db 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -100,43 +100,23 @@ class HashingDecoder { } } -class Decoder { - #tape: BytesTape - - constructor(tape: BytesTape) { - this.#tape = tape - } - - readHashing(): Hashing { - const hashingSigil = this.#tape.readVarint() - switch (hashingSigil) { - case HASHING.SHA2_512: - throw new Error(`Not implemented: hashingSigil: SHA2_512`) - case HASHING.SHA2_256: - throw new Error(`Not implemented: hashingSigil: SHA2_256`) - case HASHING.KECCAK256: - return { - kind: HASHING.KECCAK256, - } - default: - throw new UnreacheableCaseError(hashingSigil) - } - } +class CanonicalizationDecoder { + constructor(private readonly tape: BytesTape) {} - readCanonicalization(signing: Signing, hashing: Hashing) { - const sigil = this.#tape.readVarint() - const signingInput = (message: Uint8Array) => { - const m = uint8arrays.toString(message) - return keccak_256( - uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) - ) - } + read(signing: Signing, hashing: Hashing) { + const sigil = this.tape.readVarint() switch (sigil) { case CANONICALIZATION.EIP712: throw new Error(`Not implemented: readCanonicalization: EIP712`) case CANONICALIZATION.EIP191: { const signatureLen = signing.recoveryBit ? 65 : 64 - const signature = this.#tape.read(signatureLen) + const signature = this.tape.read(signatureLen) + const signingInput = (message: Uint8Array) => { + const m = uint8arrays.toString(message) + return keccak_256( + uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) + ) + } return { signing: SIGNING.SECP256K1, recoveryBit: signing.recoveryBit, @@ -148,12 +128,20 @@ class Decoder { throw new UnreacheableCaseError(sigil) } } +} + +class Decoder { + #tape: BytesTape + + constructor(tape: BytesTape) { + this.#tape = tape + } decode() { this.readVarsigSigil() const signing = SigningDecoder.read(this.#tape) const hashing = HashingDecoder.read(this.#tape) - return this.readCanonicalization(signing, hashing) + return new CanonicalizationDecoder(this.#tape).read(signing, hashing) } readVarsigSigil() { From 1642e847f52fde044305aedac8b51dcbe2558108 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:02:01 +0300 Subject: [PATCH 024/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 6717d8db..35dff8c3 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -137,9 +137,10 @@ class Decoder { this.#tape = tape } - decode() { + read() { this.readVarsigSigil() - const signing = SigningDecoder.read(this.#tape) + const signingDecoder = new SigningDecoder(this.#tape) + const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) return new CanonicalizationDecoder(this.#tape).read(signing, hashing) } @@ -183,7 +184,7 @@ test('validate eip191', async () => { signatureBytes.subarray(0, 64), ]) // const a = decode(varsig) - const a = new Decoder(new BytesTape(varsig)).decode() + const a = new Decoder(new BytesTape(varsig)).read() const input = a.signingInput(eip191canonicalization('Hello World')) let sig = secp256k1.Signature.fromCompact(a.signature) if (a.recoveryBit) { From 82a97c65b8a2fac255702e1aad33ffeb31374c84 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:07:13 +0300 Subject: [PATCH 025/110] fancy --- packages/varsig/test/parse-eip191.test.ts | 24 +++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 35dff8c3..9db567f9 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -74,6 +74,20 @@ class SigningDecoder { throw new UnreacheableCaseError(signingSigil) } } + + readSignature(signing: Signing): Uint8Array { + switch (signing.kind) { + case SIGNING.SECP256K1: { + if (signing.recoveryBit) { + return this.tape.read(65) + } else { + return this.tape.read(64) + } + } + default: + throw new UnreacheableCaseError(signing.kind) + } + } } class HashingDecoder { @@ -109,8 +123,6 @@ class CanonicalizationDecoder { case CANONICALIZATION.EIP712: throw new Error(`Not implemented: readCanonicalization: EIP712`) case CANONICALIZATION.EIP191: { - const signatureLen = signing.recoveryBit ? 65 : 64 - const signature = this.tape.read(signatureLen) const signingInput = (message: Uint8Array) => { const m = uint8arrays.toString(message) return keccak_256( @@ -120,7 +132,6 @@ class CanonicalizationDecoder { return { signing: SIGNING.SECP256K1, recoveryBit: signing.recoveryBit, - signature: signature, signingInput: signingInput, } } @@ -142,7 +153,12 @@ class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - return new CanonicalizationDecoder(this.#tape).read(signing, hashing) + const canon = new CanonicalizationDecoder(this.#tape).read(signing, hashing) + const signature = signingDecoder.readSignature(signing) + return { + ...canon, + signature: signature, + } } readVarsigSigil() { From 9e54144595af761c97585b4f453949cd59952f4b Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:07:51 +0300 Subject: [PATCH 026/110] wip --- packages/varsig/src/at0.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/varsig/src/at0.ts b/packages/varsig/src/at0.ts index 21252830..cd69ea37 100644 --- a/packages/varsig/src/at0.ts +++ b/packages/varsig/src/at0.ts @@ -9,23 +9,7 @@ export enum HASHING { KECCAK256 = 0x1b, } -export type SigningSECP256K1 = { - kind: SIGNING.SECP256K1 - recoveryBit: undefined | 27 | 28 -} - export enum CANONICALIZATION { EIP712 = 0xe712, EIP191 = 0xe191, } - -export type CanonicalizationEIP191 = { - kind: CANONICALIZATION.EIP191 -} - -export type Varsig = { - signing: TSigniing - hashing: HASHING - canonicalization: TCanonicalization - signature: Uint8Array -} From 04a30a5e5280c9c4339415a5e1c6c8d3f9881e63 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:10:10 +0300 Subject: [PATCH 027/110] wip --- packages/varsig/src/signing.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/varsig/src/signing.ts diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts new file mode 100644 index 00000000..4e2111e9 --- /dev/null +++ b/packages/varsig/src/signing.ts @@ -0,0 +1,11 @@ +export enum SigningKind { + RSASSA_PKCS1_v1_5 = 0x1205, + SECP256K1 = 0xe7, +} + +export type SigningSecp256k1 = { + kind: SigningKind.SECP256K1 + recoveryBit: number | undefined +} + +export type SigningAlgo = SigningSecp256k1 From 8a5677e61fcdac5a83b010730df24ca9b8407068 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:12:06 +0300 Subject: [PATCH 028/110] bytes-tape --- packages/varsig/src/bytes-tape.ts | 27 ++++++++++++++++++++++ packages/varsig/test/parse-eip191.test.ts | 28 +---------------------- 2 files changed, 28 insertions(+), 27 deletions(-) create mode 100644 packages/varsig/src/bytes-tape.ts diff --git a/packages/varsig/src/bytes-tape.ts b/packages/varsig/src/bytes-tape.ts new file mode 100644 index 00000000..d6d9773d --- /dev/null +++ b/packages/varsig/src/bytes-tape.ts @@ -0,0 +1,27 @@ +import { decode } from 'varintes/decode' +import type { Tape } from 'codeco/linear' + +export class BytesTape implements Tape { + position: number + + constructor(readonly input: Uint8Array) { + this.position = 0 + } + + read(n: number): Uint8Array { + const result = this.input.subarray(this.position, this.position + n) + this.position += n + return result + } + + readVarint(): T { + const bytes = this.read(10) + const [n, bytesRead] = decode(bytes) + this.position -= 10 - bytesRead + return n as T + } + + get isEOF(): boolean { + return this.position >= this.input.byteLength + } +} diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 9db567f9..784ae0c8 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -2,11 +2,10 @@ import { test } from '@jest/globals' import * as varintes from 'varintes' import { CANONICALIZATION, HASHING, SIGNING } from '../src/at0.js' import { secp256k1 } from '@noble/curves/secp256k1' -import { sha256 } from '@noble/hashes/sha256' import { keccak_256 } from '@noble/hashes/sha3' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' -import { Tape } from 'codeco/linear' +import { BytesTape } from '../src/bytes-tape.js' class UnreacheableCaseError extends Error { constructor(variant: never) { @@ -14,31 +13,6 @@ class UnreacheableCaseError extends Error { } } -class BytesTape implements Tape { - position: number - - constructor(readonly input: Uint8Array) { - this.position = 0 - } - - read(n: number): Uint8Array { - const result = this.input.subarray(this.position, this.position + n) - this.position += n - return result - } - - readVarint(): T { - const bytes = this.read(10) - const [n, bytesRead] = varintes.decode(bytes) - this.position -= 10 - bytesRead - return n as T - } - - get isEOF(): boolean { - return this.position >= this.input.byteLength - } -} - type Signing = { kind: SIGNING.SECP256K1 recoveryBit: undefined | 27 | 28 From 43685a0ae330768353a2286bd3d180c7eacf431c Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:17:00 +0300 Subject: [PATCH 029/110] wip --- packages/varsig/src/signing.ts | 47 ++++++++++++++++++- packages/varsig/src/unreachable-case-error.ts | 5 ++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 packages/varsig/src/unreachable-case-error.ts diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 4e2111e9..45a6b51b 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -1,5 +1,8 @@ +import { BytesTape } from './bytes-tape.js' +import { UnreacheableCaseError } from './unreachable-case-error' + export enum SigningKind { - RSASSA_PKCS1_v1_5 = 0x1205, + RSA = 0x1205, SECP256K1 = 0xe7, } @@ -9,3 +12,45 @@ export type SigningSecp256k1 = { } export type SigningAlgo = SigningSecp256k1 + +export class SigningDecoder { + constructor(private readonly tape: BytesTape) {} + + static read(tape: BytesTape): SigningAlgo { + return new SigningDecoder(tape).read() + } + + read(): SigningAlgo { + const signingSigil = this.tape.readVarint() + switch (signingSigil) { + case SigningKind.SECP256K1: { + const recoveryBit = this.tape.readVarint() + if (!(recoveryBit === 27 || recoveryBit === 28)) { + throw new Error(`Wrong recovery bit`) + } + return { + kind: SigningKind.SECP256K1, + recoveryBit: recoveryBit || undefined, + } + } + case SigningKind.RSA: + throw new Error(`Not implemented: signingSigil: RSA`) + default: + throw new UnreacheableCaseError(signingSigil, 'signing kind') + } + } + + readSignature(signing: SigningAlgo): Uint8Array { + switch (signing.kind) { + case SigningKind.SECP256K1: { + if (signing.recoveryBit) { + return this.tape.read(65) + } else { + return this.tape.read(64) + } + } + default: + throw new UnreacheableCaseError(signing.kind, 'signing kind') + } + } +} diff --git a/packages/varsig/src/unreachable-case-error.ts b/packages/varsig/src/unreachable-case-error.ts new file mode 100644 index 00000000..1b01d4ce --- /dev/null +++ b/packages/varsig/src/unreachable-case-error.ts @@ -0,0 +1,5 @@ +export class UnreacheableCaseError extends Error { + constructor(variant: never, kind: string) { + super(`Unreacheable case for ${kind}: ${String(variant)}`) + } +} From 105d14dfb0acfe998697367c26c70f7390d9baaa Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:18:23 +0300 Subject: [PATCH 030/110] wip --- packages/varsig/src/signing.ts | 2 +- packages/varsig/test/parse-eip191.test.ts | 50 +---------------------- 2 files changed, 3 insertions(+), 49 deletions(-) diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 45a6b51b..452a75a7 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -1,5 +1,5 @@ import { BytesTape } from './bytes-tape.js' -import { UnreacheableCaseError } from './unreachable-case-error' +import { UnreacheableCaseError } from './unreachable-case-error.js' export enum SigningKind { RSA = 0x1205, diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 784ae0c8..49f1e779 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -6,6 +6,7 @@ import { keccak_256 } from '@noble/hashes/sha3' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' +import { SigningAlgo, SigningDecoder } from '../src/signing.js' class UnreacheableCaseError extends Error { constructor(variant: never) { @@ -13,57 +14,10 @@ class UnreacheableCaseError extends Error { } } -type Signing = { - kind: SIGNING.SECP256K1 - recoveryBit: undefined | 27 | 28 -} - type Hashing = { kind: HASHING.KECCAK256 } -class SigningDecoder { - constructor(private readonly tape: BytesTape) {} - - static read(tape: BytesTape): Signing { - return new SigningDecoder(tape).read() - } - - read(): Signing { - const signingSigil = this.tape.readVarint() - switch (signingSigil) { - case SIGNING.SECP256K1: { - const recoveryBit = this.tape.readVarint() - if (!(recoveryBit === 27 || recoveryBit === 28)) { - throw new Error(`Wrong recovery bit`) - } - return { - kind: SIGNING.SECP256K1, - recoveryBit: recoveryBit || undefined, - } - } - case SIGNING.RSA: - throw new Error(`Not implemented: signingSigil: RSA`) - default: - throw new UnreacheableCaseError(signingSigil) - } - } - - readSignature(signing: Signing): Uint8Array { - switch (signing.kind) { - case SIGNING.SECP256K1: { - if (signing.recoveryBit) { - return this.tape.read(65) - } else { - return this.tape.read(64) - } - } - default: - throw new UnreacheableCaseError(signing.kind) - } - } -} - class HashingDecoder { constructor(private readonly tape: BytesTape) {} @@ -91,7 +45,7 @@ class HashingDecoder { class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - read(signing: Signing, hashing: Hashing) { + read(signing: SigningAlgo, hashing: Hashing) { const sigil = this.tape.readVarint() switch (sigil) { case CANONICALIZATION.EIP712: From 2de394b6b7ea55600b7e09959164c2196ada3632 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:21:21 +0300 Subject: [PATCH 031/110] hashing algo --- packages/varsig/src/hashing.ts | 30 +++++++++++++++++++++ packages/varsig/test/parse-eip191.test.ts | 33 +++-------------------- 2 files changed, 33 insertions(+), 30 deletions(-) create mode 100644 packages/varsig/src/hashing.ts diff --git a/packages/varsig/src/hashing.ts b/packages/varsig/src/hashing.ts new file mode 100644 index 00000000..2404a965 --- /dev/null +++ b/packages/varsig/src/hashing.ts @@ -0,0 +1,30 @@ +import { BytesTape } from './bytes-tape.js' +import { UnreacheableCaseError } from './unreachable-case-error.js' + +export enum HashingAlgo { + SHA2_256 = 0x12, + SHA2_512 = 0x13, + KECCAK256 = 0x1b, +} + +export class HashingDecoder { + constructor(private readonly tape: BytesTape) {} + + static read(tape: BytesTape) { + return new HashingDecoder(tape).read() + } + + read(): HashingAlgo { + const hashingSigil = this.tape.readVarint() + switch (hashingSigil) { + case HashingAlgo.SHA2_512: + throw new Error(`Not implemented: hashingSigil: SHA2_512`) + case HashingAlgo.SHA2_256: + throw new Error(`Not implemented: hashingSigil: SHA2_256`) + case HashingAlgo.KECCAK256: + return HashingAlgo.KECCAK256 + default: + throw new UnreacheableCaseError(hashingSigil, 'hashing algo') + } + } +} diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 49f1e779..e36c897e 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -1,12 +1,13 @@ import { test } from '@jest/globals' import * as varintes from 'varintes' -import { CANONICALIZATION, HASHING, SIGNING } from '../src/at0.js' +import { CANONICALIZATION, SIGNING } from '../src/at0.js' import { secp256k1 } from '@noble/curves/secp256k1' import { keccak_256 } from '@noble/hashes/sha3' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' import { SigningAlgo, SigningDecoder } from '../src/signing.js' +import { HashingAlgo, HashingDecoder } from '../src/hashing.js' class UnreacheableCaseError extends Error { constructor(variant: never) { @@ -14,38 +15,10 @@ class UnreacheableCaseError extends Error { } } -type Hashing = { - kind: HASHING.KECCAK256 -} - -class HashingDecoder { - constructor(private readonly tape: BytesTape) {} - - static read(tape: BytesTape) { - return new HashingDecoder(tape).read() - } - - read(): Hashing { - const hashingSigil = this.tape.readVarint() - switch (hashingSigil) { - case HASHING.SHA2_512: - throw new Error(`Not implemented: hashingSigil: SHA2_512`) - case HASHING.SHA2_256: - throw new Error(`Not implemented: hashingSigil: SHA2_256`) - case HASHING.KECCAK256: - return { - kind: HASHING.KECCAK256, - } - default: - throw new UnreacheableCaseError(hashingSigil) - } - } -} - class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - read(signing: SigningAlgo, hashing: Hashing) { + read(signing: SigningAlgo, hashing: HashingAlgo) { const sigil = this.tape.readVarint() switch (sigil) { case CANONICALIZATION.EIP712: From 2326b0834005e29835ca0e8f6c9215b68779fd37 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Tue, 21 Nov 2023 16:26:13 +0300 Subject: [PATCH 032/110] drop unused code --- packages/varsig/src/at0.ts | 15 --------------- packages/varsig/src/canonicalization.ts | 4 ++++ packages/varsig/test/parse-eip191.test.ts | 19 +++++++------------ 3 files changed, 11 insertions(+), 27 deletions(-) delete mode 100644 packages/varsig/src/at0.ts create mode 100644 packages/varsig/src/canonicalization.ts diff --git a/packages/varsig/src/at0.ts b/packages/varsig/src/at0.ts deleted file mode 100644 index cd69ea37..00000000 --- a/packages/varsig/src/at0.ts +++ /dev/null @@ -1,15 +0,0 @@ -export enum SIGNING { - RSA = 0x1205, - SECP256K1 = 0xe7, -} - -export enum HASHING { - SHA2_256 = 0x12, - SHA2_512 = 0x13, - KECCAK256 = 0x1b, -} - -export enum CANONICALIZATION { - EIP712 = 0xe712, - EIP191 = 0xe191, -} diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts new file mode 100644 index 00000000..8786e57a --- /dev/null +++ b/packages/varsig/src/canonicalization.ts @@ -0,0 +1,4 @@ +export enum CanonicalizationKind { + EIP712 = 0xe712, + EIP191 = 0xe191, +} diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index e36c897e..ad4b9c98 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -1,13 +1,13 @@ import { test } from '@jest/globals' import * as varintes from 'varintes' -import { CANONICALIZATION, SIGNING } from '../src/at0.js' import { secp256k1 } from '@noble/curves/secp256k1' import { keccak_256 } from '@noble/hashes/sha3' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' -import { SigningAlgo, SigningDecoder } from '../src/signing.js' +import { SigningAlgo, SigningDecoder, SigningKind } from '../src/signing.js' import { HashingAlgo, HashingDecoder } from '../src/hashing.js' +import { CanonicalizationKind } from '../src/canonicalization.js' class UnreacheableCaseError extends Error { constructor(variant: never) { @@ -19,11 +19,11 @@ class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} read(signing: SigningAlgo, hashing: HashingAlgo) { - const sigil = this.tape.readVarint() + const sigil = this.tape.readVarint() switch (sigil) { - case CANONICALIZATION.EIP712: + case CanonicalizationKind.EIP712: throw new Error(`Not implemented: readCanonicalization: EIP712`) - case CANONICALIZATION.EIP191: { + case CanonicalizationKind.EIP191: { const signingInput = (message: Uint8Array) => { const m = uint8arrays.toString(message) return keccak_256( @@ -31,7 +31,7 @@ class CanonicalizationDecoder { ) } return { - signing: SIGNING.SECP256K1, + signing: SigningKind.SECP256K1, recoveryBit: signing.recoveryBit, signingInput: signingInput, } @@ -67,11 +67,6 @@ class Decoder { if (sigil !== 0x34) throw new Error(`Not a varsig`) return sigil } - - readSigningSigil() { - const sigil = this.#tape.readVarint() - return sigil as SIGNING - } } function eip191canonicalization(message: string) { @@ -97,7 +92,7 @@ test('validate eip191', async () => { varintes.encode(0xe7)[0], signatureBytes.subarray(64), varintes.encode(0x1b)[0], - varintes.encode(CANONICALIZATION.EIP191)[0], + varintes.encode(CanonicalizationKind.EIP191)[0], signatureBytes.subarray(0, 64), ]) // const a = decode(varsig) From 1fb82f47137f4caf22f8feb9a2189b6b7c75b908 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Tue, 21 Nov 2023 16:27:29 +0300 Subject: [PATCH 033/110] Encode signatures eip712 --- packages/varsig/src/bytes-tape.ts | 4 ++ packages/varsig/src/encoding/eip712.ts | 38 ++++++++++++--- packages/varsig/test/encoding/eip712.test.ts | 50 ++++++++++++-------- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/packages/varsig/src/bytes-tape.ts b/packages/varsig/src/bytes-tape.ts index d6d9773d..ed8ea424 100644 --- a/packages/varsig/src/bytes-tape.ts +++ b/packages/varsig/src/bytes-tape.ts @@ -21,6 +21,10 @@ export class BytesTape implements Tape { return n as T } + get remainder(): Uint8Array { + return this.input.subarray(this.position) + } + get isEOF(): boolean { return this.position >= this.input.byteLength } diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index 0fed6e37..b2e63dd4 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -18,11 +18,18 @@ interface Eip712TypeField { type Eip712Types = Record> +interface SignatureComponents { + r: string + s: string + v: number +} + interface Eip712 { types: Eip712Types domain: Eip712Domain primaryType: string message?: Record + signature?: string | SignatureComponents } type CompressedType = [string, string][] @@ -100,20 +107,37 @@ function ipldNodeToMessage(node: IpldNode): Record { return message } -type IpldEip712 = any +interface IpldEip712 { + _sig: Uint8Array + [key: string]: any +} -export function fromEip712({ types, domain, primaryType, message }: Eip712): { +export function fromEip712({ types, domain, primaryType, message, signature }: Eip712): { node: IpldEip712 params: Uint8Array } { const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] - return { - // @ts-ignore - node: messageToIpld(message, types, primaryType), - params: uint8arrays.concat([metadataLength, metadataBytes]), - } + const recoveryBit = signature.v ? + new Uint8Array([signature.v]) : + uint8arrays.fromString(signature.slice(-2), 'base16') + const signatureBytes = signature.r ? + uint8arrays.fromString(signature.r.slice(2) + signature.s.slice(2), 'base16') : + uint8arrays.fromString(signature.slice(2, -2), 'base16') + const varsig = uint8arrays.concat([ + new Uint8Array([0x34]), // varsig sigil + varintes.encode(0xe7)[0], // key type + recoveryBit, + varintes.encode(0x1b)[0], // hash type + varintes.encode(0xe712)[0], // canonicalizer codec + metadataLength, + metadataBytes, + signatureBytes + ]) + const node = messageToIpld(message, types, primaryType) + node._sig = varsig + return node } function messageToIpld( diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts index 17200d53..b3e0e5fb 100644 --- a/packages/varsig/test/encoding/eip712.test.ts +++ b/packages/varsig/test/encoding/eip712.test.ts @@ -1,5 +1,13 @@ import { fromEip712, setupCanonicalizer } from '../../src/encoding/eip712.ts' +import { BytesTape } from '../../src/bytes-tape.ts' import * as uint8arrays from 'uint8arrays' +import { createWalletClient, custom } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +const walletClient = createWalletClient({ + account: privateKeyToAccount('0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504'), + transport: custom({ request: async () => {}}) +}) const testData = { "types": { @@ -68,35 +76,39 @@ const testData = { }, "contents": "Hello, Bob!", "attachment": "0xababababababababababa83459873459873459873498575986734359" - } + }, + "signature": "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b" } const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') test('Encode eip712 message', async () => { - const enc = fromEip712(testData) + const node = fromEip712(testData) - expect(enc.params.length).toEqual(196) - expect(enc.node.attachment instanceof Uint8Array).toBeTruthy() - console.log(enc.node) + expect(node._sig.length).toEqual(268) + expect(node.attachment instanceof Uint8Array).toBeTruthy() + console.log(node) }) + test('Canonicalize ipld eip712 object', async () => { - const enc = fromEip712(testData) - const can1 = setupCanonicalizer(enc.params) - expect(can1.remainder.length).toEqual(0) - const res1 = can1.canonicalize(enc.node) + const node = fromEip712(testData) + const tape = new BytesTape(node._sig) + tape.readVarint() // skip sigil + tape.readVarint() // skip key type + tape.read(1) // skip recovery bit + tape.readVarint() // skip hash type + tape.readVarint() // skip canonicalizer codec + const can1 = setupCanonicalizer(tape.remainder) + expect(can1.remainder.length).toEqual(64) + delete node._sig + delete testData.signature + const res1 = can1.canonicalize(node) expect(res1.decoded).toEqual(testData) expect(res1.digest).toEqual(expectedHash) +}) - // extra remainder should not affect parsing - const testRemainder = new Uint8Array([1, 2, 3]) - const can2 = setupCanonicalizer( - uint8arrays.concat([enc.params, testRemainder]) - ) - expect(can2.remainder.length).toEqual(testRemainder.length) - expect(can2.remainder).toEqual(testRemainder) - const res2 = can2.canonicalize(enc.node) - expect(res2.decoded).toEqual(testData) - expect(res2.digest).toEqual(expectedHash) +test('Generate test vectors', async () => { + const signature = await walletClient.signTypedData(testData) + console.log('sig', signature) }) From 40201a498258f8f3291dc0deaebb5ec691e5b787 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 11:20:41 +0300 Subject: [PATCH 034/110] wip --- packages/varsig/src/canonicalization.ts | 41 ++++++++++++++++++ packages/varsig/test/parse-eip191.test.ts | 52 ++++------------------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 8786e57a..d65fd1ba 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -1,4 +1,45 @@ +import { BytesTape } from './bytes-tape.js' +import { SigningAlgo, SigningKind } from './signing.js' +import { HashingAlgo } from './hashing.js' +import * as uint8arrays from 'uint8arrays' +import { keccak_256 } from '@noble/hashes/sha3' +import { UnreacheableCaseError } from './unreachable-case-error.js' + export enum CanonicalizationKind { EIP712 = 0xe712, EIP191 = 0xe191, } + +type CanonicalizationEIP191 = { + kind: CanonicalizationKind + (message: string): Uint8Array +} + +type Canonicalization = CanonicalizationEIP191 + +export class CanonicalizationDecoder { + constructor(private readonly tape: BytesTape) {} + + read(signing: SigningAlgo, hashing: HashingAlgo) { + const sigil = this.tape.readVarint() + switch (sigil) { + case CanonicalizationKind.EIP712: + throw new Error(`Not implemented: readCanonicalization: EIP712`) + case CanonicalizationKind.EIP191: { + const signingInput = (message: Uint8Array) => { + const m = uint8arrays.toString(message) + return keccak_256( + uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) + ) + } + return { + signing: SigningKind.SECP256K1, + recoveryBit: signing.recoveryBit, + signingInput: signingInput, + } + } + default: + throw new UnreacheableCaseError(sigil, 'canonicalization kind') + } + } +} diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index ad4b9c98..73338ada 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -1,46 +1,12 @@ import { test } from '@jest/globals' import * as varintes from 'varintes' import { secp256k1 } from '@noble/curves/secp256k1' -import { keccak_256 } from '@noble/hashes/sha3' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' -import { SigningAlgo, SigningDecoder, SigningKind } from '../src/signing.js' -import { HashingAlgo, HashingDecoder } from '../src/hashing.js' -import { CanonicalizationKind } from '../src/canonicalization.js' - -class UnreacheableCaseError extends Error { - constructor(variant: never) { - super(`Unreacheable case: ${String(variant)}`) - } -} - -class CanonicalizationDecoder { - constructor(private readonly tape: BytesTape) {} - - read(signing: SigningAlgo, hashing: HashingAlgo) { - const sigil = this.tape.readVarint() - switch (sigil) { - case CanonicalizationKind.EIP712: - throw new Error(`Not implemented: readCanonicalization: EIP712`) - case CanonicalizationKind.EIP191: { - const signingInput = (message: Uint8Array) => { - const m = uint8arrays.toString(message) - return keccak_256( - uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) - ) - } - return { - signing: SigningKind.SECP256K1, - recoveryBit: signing.recoveryBit, - signingInput: signingInput, - } - } - default: - throw new UnreacheableCaseError(sigil) - } - } -} +import { SigningDecoder } from '../src/signing.js' +import { HashingDecoder } from '../src/hashing.js' +import { CanonicalizationDecoder, CanonicalizationKind } from '../src/canonicalization.js' class Decoder { #tape: BytesTape @@ -96,11 +62,11 @@ test('validate eip191', async () => { signatureBytes.subarray(0, 64), ]) // const a = decode(varsig) - const a = new Decoder(new BytesTape(varsig)).read() - const input = a.signingInput(eip191canonicalization('Hello World')) - let sig = secp256k1.Signature.fromCompact(a.signature) - if (a.recoveryBit) { - sig = sig.addRecoveryBit(a.recoveryBit - 27) + const decoder = new Decoder(new BytesTape(varsig)).read() + const input = decoder.signingInput(eip191canonicalization('Hello World')) + let signature = secp256k1.Signature.fromCompact(decoder.signature) + if (decoder.recoveryBit) { + signature = signature.addRecoveryBit(decoder.recoveryBit - 27) } - console.log(sig.recoverPublicKey(input).toHex(false)) + console.log(signature.recoverPublicKey(input).toHex(false)) }) From 53560acfbe3a52076b871b035dd91d7898c3af0f Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 11:23:18 +0300 Subject: [PATCH 035/110] wip --- packages/varsig/src/canonicalization.ts | 18 ++++++++---------- packages/varsig/test/parse-eip191.test.ts | 12 +++++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index d65fd1ba..36434563 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -1,5 +1,5 @@ import { BytesTape } from './bytes-tape.js' -import { SigningAlgo, SigningKind } from './signing.js' +import { SigningAlgo } from './signing.js' import { HashingAlgo } from './hashing.js' import * as uint8arrays from 'uint8arrays' import { keccak_256 } from '@noble/hashes/sha3' @@ -20,23 +20,21 @@ type Canonicalization = CanonicalizationEIP191 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - read(signing: SigningAlgo, hashing: HashingAlgo) { + read(signing: SigningAlgo, hashing: HashingAlgo): Canonicalization { const sigil = this.tape.readVarint() switch (sigil) { case CanonicalizationKind.EIP712: throw new Error(`Not implemented: readCanonicalization: EIP712`) case CanonicalizationKind.EIP191: { - const signingInput = (message: Uint8Array) => { - const m = uint8arrays.toString(message) + const signingInput = (message: string) => { return keccak_256( - uint8arrays.fromString(`\x19Ethereum Signed Message:\n` + String(m.length) + m) + uint8arrays.fromString( + `\x19Ethereum Signed Message:\n` + String(message.length) + message + ) ) } - return { - signing: SigningKind.SECP256K1, - recoveryBit: signing.recoveryBit, - signingInput: signingInput, - } + signingInput.kind = CanonicalizationKind.EIP191 + return signingInput } default: throw new UnreacheableCaseError(sigil, 'canonicalization kind') diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 73338ada..e5d30b5a 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -20,10 +20,12 @@ class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - const canon = new CanonicalizationDecoder(this.#tape).read(signing, hashing) + const canonicalization = new CanonicalizationDecoder(this.#tape).read(signing, hashing) const signature = signingDecoder.readSignature(signing) return { - ...canon, + signing: signing, + hashing: hashing, + canonicalization: canonicalization, signature: signature, } } @@ -63,10 +65,10 @@ test('validate eip191', async () => { ]) // const a = decode(varsig) const decoder = new Decoder(new BytesTape(varsig)).read() - const input = decoder.signingInput(eip191canonicalization('Hello World')) + const input = decoder.canonicalization('Hello World') let signature = secp256k1.Signature.fromCompact(decoder.signature) - if (decoder.recoveryBit) { - signature = signature.addRecoveryBit(decoder.recoveryBit - 27) + if (decoder.signing.recoveryBit) { + signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) } console.log(signature.recoverPublicKey(input).toHex(false)) }) From e1ec6890a87c20090e14acca3495fdcc9d54259f Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:08:33 +0300 Subject: [PATCH 036/110] 712 --- packages/varsig/src/canonicalization.ts | 21 ++- packages/varsig/src/encoding/eip712.ts | 4 +- packages/varsig/test/parse-eip712.test.ts | 216 ++++++++++++++++++++++ 3 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 packages/varsig/test/parse-eip712.test.ts diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 36434563..fa493315 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -4,6 +4,8 @@ import { HashingAlgo } from './hashing.js' import * as uint8arrays from 'uint8arrays' import { keccak_256 } from '@noble/hashes/sha3' import { UnreacheableCaseError } from './unreachable-case-error.js' +import { hashTypedData } from 'viem' +import { decompressDomain, decompressTypes } from './encoding/eip712.js' export enum CanonicalizationKind { EIP712 = 0xe712, @@ -23,8 +25,23 @@ export class CanonicalizationDecoder { read(signing: SigningAlgo, hashing: HashingAlgo): Canonicalization { const sigil = this.tape.readVarint() switch (sigil) { - case CanonicalizationKind.EIP712: - throw new Error(`Not implemented: readCanonicalization: EIP712`) + case CanonicalizationKind.EIP712: { + const metadataLength = this.tape.readVarint() + const metadataBytes = this.tape.read(metadataLength) + const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) + const [types, primaryType, domain] = metadata + const signingInput = (message: any) => { + const digestHex = hashTypedData({ + domain: decompressDomain(domain), + message: message, + primaryType: primaryType, + types: decompressTypes(types), + }) + return uint8arrays.fromString(digestHex.toLowerCase().replace(/^0x/, ''), 'hex') + } + signingInput.kind = CanonicalizationKind.EIP712 + return signingInput + } case CanonicalizationKind.EIP191: { const signingInput = (message: string) => { return keccak_256( diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index 0fed6e37..780434dc 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -152,7 +152,7 @@ function compressDomain(domain: Eip712Domain): CompressedDomain { return [domain.name, domain.version, domain.chainId, domain.verifyingContract] } -function decompressDomain(domain: CompressedDomain): Eip712Domain { +export function decompressDomain(domain: CompressedDomain): Eip712Domain { return { name: domain[0], version: domain[1], @@ -189,7 +189,7 @@ const FULL_TYPES = { o: 'bool', } -function decompressTypes(compressed: CompressedTypes): Eip712Types { +export function decompressTypes(compressed: CompressedTypes): Eip712Types { const types = { EIP712Domain: EIP712_DOMAIN } for (const [key, value] of Object.entries(compressed)) { // @ts-ignore diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts new file mode 100644 index 00000000..e4c6eeed --- /dev/null +++ b/packages/varsig/test/parse-eip712.test.ts @@ -0,0 +1,216 @@ +import { test } from '@jest/globals' +import * as varintes from 'varintes' +import { secp256k1 } from '@noble/curves/secp256k1' +import * as uint8arrays from 'uint8arrays' +import { privateKeyToAccount } from 'viem/accounts' +import { BytesTape } from '../src/bytes-tape.js' +import { SigningDecoder } from '../src/signing.js' +import { HashingDecoder } from '../src/hashing.js' +import { CanonicalizationDecoder, CanonicalizationKind } from '../src/canonicalization.js' +import { fromEip712, setupCanonicalizer } from '../src/encoding/eip712' + +const testData = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Person: [ + { + name: 'name', + type: 'string', + }, + { + name: 'wallet', + type: 'address', + }, + ], + Mail: [ + { + name: 'from', + type: 'Person', + }, + { + name: 'to', + type: 'Person', + }, + { + name: 'contents', + type: 'string', + }, + { + name: 'attachment', + type: 'bytes', + }, + ], + }, + primaryType: 'Mail' as const, + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + attachment: '0xababababababababababa83459873459873459873498575986734359', + }, +} as const + +const expectedHash = uint8arrays.fromString( + '703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', + 'base16' +) + +class Decoder { + #tape: BytesTape + + constructor(tape: BytesTape) { + this.#tape = tape + } + + read() { + this.readVarsigSigil() + const signingDecoder = new SigningDecoder(this.#tape) + const signing = signingDecoder.read() + const hashing = HashingDecoder.read(this.#tape) + const canonicalization = new CanonicalizationDecoder(this.#tape).read(signing, hashing) + const signature = signingDecoder.readSignature(signing) + return { + signing: signing, + hashing: hashing, + canonicalization: canonicalization, + signature: signature, + } + } + + readVarsigSigil() { + const sigil = this.#tape.readVarint() + if (sigil !== 0x34) throw new Error(`Not a varsig`) + return sigil + } +} + +function eip191canonicalization(message: string) { + return uint8arrays.fromString(message) +} + +function hex(...numbers: Array): Uint8Array { + return new Uint8Array(numbers) +} + +// test('validate eip191', async () => { +// const account = privateKeyToAccount( +// '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' +// ) +// console.log('pub', account.publicKey) +// const stringSignature = await account.signMessage({ message: 'Hello World' }) +// const signatureBytes = uint8arrays.fromString( +// stringSignature.toLowerCase().replace(/^0x/, ''), +// 'hex' +// ) +// const varsig = uint8arrays.concat([ +// hex(0x34), +// varintes.encode(0xe7)[0], +// signatureBytes.subarray(64), +// varintes.encode(0x1b)[0], +// varintes.encode(CanonicalizationKind.EIP191)[0], +// signatureBytes.subarray(0, 64), +// ]) +// // const a = decode(varsig) +// const decoder = new Decoder(new BytesTape(varsig)).read() +// const input = decoder.canonicalization('Hello World') +// let signature = secp256k1.Signature.fromCompact(decoder.signature) +// if (decoder.signing.recoveryBit) { +// signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) +// } +// console.log(signature.recoverPublicKey(input).toHex(false)) +// }) + +test('Encode eip712 message', async () => { + const enc = fromEip712(testData) + + expect(enc.params.length).toEqual(196) + expect(enc.node.attachment instanceof Uint8Array).toBeTruthy() + // console.log(enc.node) +}) + +test('Canonicalize ipld eip712 object', async () => { + const enc = fromEip712(testData) + const can1 = setupCanonicalizer(enc.params) + expect(can1.remainder.length).toEqual(0) + const res1 = can1.canonicalize(enc.node) + expect(res1.decoded).toEqual(testData) + expect(res1.digest).toEqual(expectedHash) + + // extra remainder should not affect parsing + const testRemainder = new Uint8Array([1, 2, 3]) + const can2 = setupCanonicalizer(uint8arrays.concat([enc.params, testRemainder])) + expect(can2.remainder.length).toEqual(testRemainder.length) + expect(can2.remainder).toEqual(testRemainder) + const res2 = can2.canonicalize(enc.node) + expect(res2.decoded).toEqual(testData) + expect(res2.digest).toEqual(expectedHash) +}) + +test('712 flow', async () => { + const account = privateKeyToAccount( + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + ) + console.log('pub.0', account.publicKey) + const stringSignature = await account.signTypedData({ + domain: { ...testData.domain, chainId: 1n }, + types: testData.types, + primaryType: testData.primaryType, + message: testData.message, + }) + const signatureBytes = uint8arrays.fromString( + stringSignature.toLowerCase().replace(/^0x/, ''), + 'hex' + ) + const a = fromEip712({ + // @ts-ignore + types: testData.types, + domain: testData.domain, + primaryType: testData.primaryType, + message: testData.message, + }) + const varsig = uint8arrays.concat([ + hex(0x34), + varintes.encode(0xe7)[0], + signatureBytes.subarray(64), + varintes.encode(0x1b)[0], + varintes.encode(CanonicalizationKind.EIP712)[0], + a.params, + signatureBytes.subarray(0, 64), + ]) + const decoder = new Decoder(new BytesTape(varsig)).read() + const input = decoder.canonicalization(testData.message) + let signature = secp256k1.Signature.fromCompact(decoder.signature) + if (decoder.signing.recoveryBit) { + signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) + } + console.log('pub.1', signature.recoverPublicKey(input).toHex(false)) +}) From f7a56e40ad6a57dab67d33d8069a430bc321af05 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:22:03 +0300 Subject: [PATCH 037/110] make it work --- packages/varsig/src/encoding/eip712.ts | 36 ++++++++++++++++++----- packages/varsig/test/parse-eip712.test.ts | 4 +-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index 501c8bfd..722f8b1c 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -119,12 +119,12 @@ export function fromEip712({ types, domain, primaryType, message, signature }: E const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] - const recoveryBit = signature.v ? - new Uint8Array([signature.v]) : - uint8arrays.fromString(signature.slice(-2), 'base16') - const signatureBytes = signature.r ? - uint8arrays.fromString(signature.r.slice(2) + signature.s.slice(2), 'base16') : - uint8arrays.fromString(signature.slice(2, -2), 'base16') + const recoveryBit = signature.v + ? new Uint8Array([signature.v]) + : uint8arrays.fromString(signature.slice(-2), 'base16') + const signatureBytes = signature.r + ? uint8arrays.fromString(signature.r.slice(2) + signature.s.slice(2), 'base16') + : uint8arrays.fromString(signature.slice(2, -2), 'base16') const varsig = uint8arrays.concat([ new Uint8Array([0x34]), // varsig sigil varintes.encode(0xe7)[0], // key type @@ -133,13 +133,35 @@ export function fromEip712({ types, domain, primaryType, message, signature }: E varintes.encode(0xe712)[0], // canonicalizer codec metadataLength, metadataBytes, - signatureBytes + signatureBytes, ]) const node = messageToIpld(message, types, primaryType) node._sig = varsig return node } +export function fromEip712A({ types, domain, primaryType, message }: Omit): { + node: IpldEip712 + params: Uint8Array +} { + const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) + const metadataBytes = uint8arrays.fromString(metadata) + const metadataLength = varintes.encode(metadataBytes.length)[0] + const varsig = uint8arrays.concat([ + // new Uint8Array([0x34]), // varsig sigil + // varintes.encode(0xe7)[0], // key type + // varintes.encode(0x1b)[0], // hash type + // varintes.encode(0xe712)[0], // canonicalizer codec + metadataLength, + metadataBytes, + ]) + const node = messageToIpld(message, types, primaryType) + node._sig = varsig + return { + params: varsig + } +} + function messageToIpld( message: Record, types: Eip712Types, diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index e4c6eeed..30f84015 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -7,7 +7,7 @@ import { BytesTape } from '../src/bytes-tape.js' import { SigningDecoder } from '../src/signing.js' import { HashingDecoder } from '../src/hashing.js' import { CanonicalizationDecoder, CanonicalizationKind } from '../src/canonicalization.js' -import { fromEip712, setupCanonicalizer } from '../src/encoding/eip712' +import {fromEip712, fromEip712A, setupCanonicalizer} from '../src/encoding/eip712' const testData = { types: { @@ -190,7 +190,7 @@ test('712 flow', async () => { stringSignature.toLowerCase().replace(/^0x/, ''), 'hex' ) - const a = fromEip712({ + const a = fromEip712A({ // @ts-ignore types: testData.types, domain: testData.domain, From 77f1e044698b09627de71b50f66d9a0c1106bbe7 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:39:08 +0300 Subject: [PATCH 038/110] make it work --- packages/varsig/src/canonicalization.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index fa493315..0d02c739 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -13,11 +13,16 @@ export enum CanonicalizationKind { } type CanonicalizationEIP191 = { - kind: CanonicalizationKind + kind: CanonicalizationKind.EIP191 (message: string): Uint8Array } -type Canonicalization = CanonicalizationEIP191 +type CanonicalizationEIP712 = { + kind: CanonicalizationKind.EIP712 + (message: string): Uint8Array +} + +type Canonicalization = CanonicalizationEIP191 | CanonicalizationEIP712 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} From 03c04824441bdf5af57a627b9300be2820b71102 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:39:54 +0300 Subject: [PATCH 039/110] wip --- packages/varsig/test/parse-eip712.test.ts | 28 +---------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index 30f84015..f64d1482 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -7,7 +7,7 @@ import { BytesTape } from '../src/bytes-tape.js' import { SigningDecoder } from '../src/signing.js' import { HashingDecoder } from '../src/hashing.js' import { CanonicalizationDecoder, CanonicalizationKind } from '../src/canonicalization.js' -import {fromEip712, fromEip712A, setupCanonicalizer} from '../src/encoding/eip712' +import { fromEip712A } from '../src/encoding/eip712' const testData = { types: { @@ -149,32 +149,6 @@ function hex(...numbers: Array): Uint8Array { // console.log(signature.recoverPublicKey(input).toHex(false)) // }) -test('Encode eip712 message', async () => { - const enc = fromEip712(testData) - - expect(enc.params.length).toEqual(196) - expect(enc.node.attachment instanceof Uint8Array).toBeTruthy() - // console.log(enc.node) -}) - -test('Canonicalize ipld eip712 object', async () => { - const enc = fromEip712(testData) - const can1 = setupCanonicalizer(enc.params) - expect(can1.remainder.length).toEqual(0) - const res1 = can1.canonicalize(enc.node) - expect(res1.decoded).toEqual(testData) - expect(res1.digest).toEqual(expectedHash) - - // extra remainder should not affect parsing - const testRemainder = new Uint8Array([1, 2, 3]) - const can2 = setupCanonicalizer(uint8arrays.concat([enc.params, testRemainder])) - expect(can2.remainder.length).toEqual(testRemainder.length) - expect(can2.remainder).toEqual(testRemainder) - const res2 = can2.canonicalize(enc.node) - expect(res2.decoded).toEqual(testData) - expect(res2.digest).toEqual(expectedHash) -}) - test('712 flow', async () => { const account = privateKeyToAccount( '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' From 198e9c2fedb709f89cf5b2fe869d662facc7f5a0 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:40:11 +0300 Subject: [PATCH 040/110] wip --- packages/varsig/src/canonicalization.ts | 2 +- packages/varsig/test/parse-eip191.test.ts | 2 +- packages/varsig/test/parse-eip712.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 0d02c739..1f3dd762 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -27,7 +27,7 @@ type Canonicalization = CanonicalizationEIP191 | CanonicalizationEIP712 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - read(signing: SigningAlgo, hashing: HashingAlgo): Canonicalization { + read(): Canonicalization { const sigil = this.tape.readVarint() switch (sigil) { case CanonicalizationKind.EIP712: { diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index e5d30b5a..793745e5 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -20,7 +20,7 @@ class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read(signing, hashing) + const canonicalization = new CanonicalizationDecoder(this.#tape).read() const signature = signingDecoder.readSignature(signing) return { signing: signing, diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index f64d1482..6ea75c93 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -96,7 +96,7 @@ class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read(signing, hashing) + const canonicalization = new CanonicalizationDecoder(this.#tape).read() const signature = signingDecoder.readSignature(signing) return { signing: signing, From cbf2fc74c94ea7f7e132d0f88e481137147f7a33 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:48:13 +0300 Subject: [PATCH 041/110] wip --- packages/varsig/src/canonicalization.ts | 12 +++--- packages/varsig/src/encoding/eip712.ts | 14 +++--- packages/varsig/test/parse-eip712.test.ts | 52 +++++++---------------- 3 files changed, 28 insertions(+), 50 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 1f3dd762..67ea2362 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -1,11 +1,9 @@ import { BytesTape } from './bytes-tape.js' -import { SigningAlgo } from './signing.js' -import { HashingAlgo } from './hashing.js' import * as uint8arrays from 'uint8arrays' import { keccak_256 } from '@noble/hashes/sha3' import { UnreacheableCaseError } from './unreachable-case-error.js' import { hashTypedData } from 'viem' -import { decompressDomain, decompressTypes } from './encoding/eip712.js' +import { CompressedDomain, decompressDomain, decompressTypes } from './encoding/eip712.js' export enum CanonicalizationKind { EIP712 = 0xe712, @@ -19,15 +17,15 @@ type CanonicalizationEIP191 = { type CanonicalizationEIP712 = { kind: CanonicalizationKind.EIP712 - (message: string): Uint8Array + (message: any): Uint8Array } -type Canonicalization = CanonicalizationEIP191 | CanonicalizationEIP712 +export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP712 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - read(): Canonicalization { + read(): CanonicalizationAlgo { const sigil = this.tape.readVarint() switch (sigil) { case CanonicalizationKind.EIP712: { @@ -37,7 +35,7 @@ export class CanonicalizationDecoder { const [types, primaryType, domain] = metadata const signingInput = (message: any) => { const digestHex = hashTypedData({ - domain: decompressDomain(domain), + domain: decompressDomain(domain as CompressedDomain), message: message, primaryType: primaryType, types: decompressTypes(types), diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index 722f8b1c..ae43ae54 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -1,6 +1,6 @@ import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' -import { hashTypedData } from 'viem' +import { hashTypedData, Hex, TypedDataDomain } from 'viem' interface Eip712Domain { name: string @@ -32,9 +32,9 @@ interface Eip712 { signature?: string | SignatureComponents } -type CompressedType = [string, string][] -type CompressedTypes = Record -type CompressedDomain = [string, string, number, string] +export type CompressedType = Array<[string, string]> +export type CompressedTypes = Record +export type CompressedDomain = [string, string, number, Hex] interface CanonicalizerResult { digest: Uint8Array @@ -158,7 +158,7 @@ export function fromEip712A({ types, domain, primaryType, message }: Omit): Uint8Array { return new Uint8Array(numbers) } -// test('validate eip191', async () => { -// const account = privateKeyToAccount( -// '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' -// ) -// console.log('pub', account.publicKey) -// const stringSignature = await account.signMessage({ message: 'Hello World' }) -// const signatureBytes = uint8arrays.fromString( -// stringSignature.toLowerCase().replace(/^0x/, ''), -// 'hex' -// ) -// const varsig = uint8arrays.concat([ -// hex(0x34), -// varintes.encode(0xe7)[0], -// signatureBytes.subarray(64), -// varintes.encode(0x1b)[0], -// varintes.encode(CanonicalizationKind.EIP191)[0], -// signatureBytes.subarray(0, 64), -// ]) -// // const a = decode(varsig) -// const decoder = new Decoder(new BytesTape(varsig)).read() -// const input = decoder.canonicalization('Hello World') -// let signature = secp256k1.Signature.fromCompact(decoder.signature) -// if (decoder.signing.recoveryBit) { -// signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) -// } -// console.log(signature.recoverPublicKey(input).toHex(false)) -// }) - test('712 flow', async () => { const account = privateKeyToAccount( '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' @@ -181,6 +160,7 @@ test('712 flow', async () => { signatureBytes.subarray(0, 64), ]) const decoder = new Decoder(new BytesTape(varsig)).read() + if (decoder.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) const input = decoder.canonicalization(testData.message) let signature = secp256k1.Signature.fromCompact(decoder.signature) if (decoder.signing.recoveryBit) { From 270324a3fd62a5cd9dbf1bba0ed03ad34d23e633 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:53:43 +0300 Subject: [PATCH 042/110] remove unused varsig.ts experiment --- packages/varsig/src/varsig.ts | 32 ------------- packages/varsig/test/varsig.test.ts | 71 ----------------------------- 2 files changed, 103 deletions(-) delete mode 100644 packages/varsig/src/varsig.ts delete mode 100644 packages/varsig/test/varsig.test.ts diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts deleted file mode 100644 index 54f926df..00000000 --- a/packages/varsig/src/varsig.ts +++ /dev/null @@ -1,32 +0,0 @@ -export enum SIGNING { - RSA = 0x1205, - ED25519 = 0xed, - ES256 = 0x12, -} - -export enum HASHING { - SHA2_256 = 0x12, - SHA2_512 = 0x13, -} - -export enum ENCODING { - IDENTITY = 0x00, - JWT = 0x01, -} - -export type Varsig = { - encoding: ENCODING - hashing: HASHING - signing: SIGNING - payload: Record - signature: Uint8Array -} - -// Sign an IPLD -// Here is EIP1271 blob please encode -// or here is JWT please encode it - -// export function verify(signingInput: Uint8Array, bytes: Uint8Array, offset = 0) { -// const input = bytes.subarray(offset) -// const varsig = fromBytes(input) -// } diff --git a/packages/varsig/test/varsig.test.ts b/packages/varsig/test/varsig.test.ts deleted file mode 100644 index 26769983..00000000 --- a/packages/varsig/test/varsig.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { test } from '@jest/globals' -import { toBytes } from '../src/bytes.js' -import { ENCODING, HASHING, SIGNING, Varsig } from '../src/varsig.js' -import * as uint8arrays from 'uint8arrays' -import { Ed25519Provider } from 'key-did-provider-ed25519' -import KeyResolver from 'key-did-resolver' -import { DID, GeneralJWS } from 'dids' -import { randomBytes } from '@stablelib/random' - -test('rsa', async () => { - const key = await crypto.subtle.generateKey( - { - name: 'RSASSA-PKCS1-v1_5', - modulusLength: 4096, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: 'SHA-256', - }, - true, - ['sign', 'verify'] - ) - const a = await crypto.subtle.sign( - { - name: 'RSASSA-PKCS1-v1_5', - }, - key.privateKey, - new Uint8Array([1, 2, 3]) - ) - console.log('verif', await crypto.subtle.verify({ name: 'RSASSA-PKCS1-v1_5'}, key.publicKey, a, new Uint8Array([1, 2, 3]))) - const signatureBytes = new Uint8Array(a) - const b = toIPLD({ - payload: {}, - encoding: ENCODING.IDENTITY, - hashing: HASHING.SHA2_256, - signing: SIGNING.RSA, - signature: signatureBytes, - }) - // console.log('b', b) -}) - -function toIPLD(input: Varsig) { - return { - ...input.payload, - _signature: toBytes({ - encoding: input.encoding, - hashing: input.hashing, - signing: input.signing, - signature: input.signature, - }), - } -} - -function jwsToIPLD(jws: GeneralJWS) { - const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(jws.payload, 'base64url'))) - console.log('payload', payload) - const signature0 = jws.signatures[0] - const protectedHeader = JSON.parse( - uint8arrays.toString(uint8arrays.fromString(signature0.protected, 'base64url')) - ) - console.log('protected', protectedHeader) -} - -test('jwt-to-ipld', async () => { - const seed = randomBytes(32) - const provider = new Ed25519Provider(seed) - const did = new DID({ provider, resolver: KeyResolver.getResolver() }) - await did.authenticate() - const jws = await did.createJWS({ hello: 'world' }) - console.log('jws', jws) - jwsToIPLD(jws) - console.log('dagjws', await did.createDagJWS({ hello: 'world' })) -}) From c72d31061b401bdf05a005b4a067e859037362e8 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 12:54:08 +0300 Subject: [PATCH 043/110] remove unused varbytes.ts experiment --- packages/varsig/src/varbytes.ts | 17 -------------- packages/varsig/test/varbytes.test.ts | 32 --------------------------- 2 files changed, 49 deletions(-) delete mode 100644 packages/varsig/src/varbytes.ts delete mode 100644 packages/varsig/test/varbytes.test.ts diff --git a/packages/varsig/src/varbytes.ts b/packages/varsig/src/varbytes.ts deleted file mode 100644 index cdd9b413..00000000 --- a/packages/varsig/src/varbytes.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as varintes from 'varintes' - -export function encode(bytes: Uint8Array, out?: Uint8Array, offset?: number): [Uint8Array, number] { - const byteLength = bytes.byteLength - const [lenBytes, lenLen] = varintes.encode(byteLength) - const destination = out ? out.subarray(offset) : new Uint8Array(lenLen + byteLength) - destination.set(lenBytes, 0) - destination.set(bytes, lenLen) - return [destination, lenLen + byteLength] -} - -export function decode(buffer: Uint8Array, offset = 0): [Uint8Array, number] { - const bytes = buffer.subarray(offset) - const [len, lenLen] = varintes.decode(bytes) - const result = bytes.subarray(lenLen, len + lenLen) - return [result, result.byteLength] -} diff --git a/packages/varsig/test/varbytes.test.ts b/packages/varsig/test/varbytes.test.ts deleted file mode 100644 index 8ff94ca8..00000000 --- a/packages/varsig/test/varbytes.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { test, expect } from '@jest/globals' -import * as varintes from 'varintes' -import * as varbytes from '../src/varbytes.js' -import { randomBytes } from '@stablelib/random' -import { concat } from 'uint8arrays' - -test('single-byte len', () => { - const input = new Uint8Array([20]) - const [encoded] = varbytes.encode(input) - expect(encoded).toEqual(new Uint8Array([1, 20])) - const [decoded] = varbytes.decode(encoded) - expect(decoded).toEqual(input) -}) - -test('multi-byte len', () => { - const input = randomBytes(256) - const [encoded] = varbytes.encode(input) - const [len] = varintes.encode(input.byteLength) - expect(encoded).toEqual(concat([len, input])) - const [decoded] = varbytes.decode(encoded) - expect(decoded).toEqual(input) -}) - -test('with remainder', () => { - const input = randomBytes(256) - const [encoded] = varbytes.encode(input) - const full = concat([encoded, randomBytes(256)]) - const [len] = varintes.encode(input.byteLength) - expect(encoded).toEqual(concat([len, input])) - const [decoded] = varbytes.decode(full) - expect(decoded).toEqual(input) -}) From c70d87766456b558d214932a167cd367d6c22a1d Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 13:29:31 +0300 Subject: [PATCH 044/110] signing test --- packages/varsig/src/__tests__/hex.util.ts | 3 + packages/varsig/src/__tests__/signing.test.ts | 65 +++++++++++++++++++ packages/varsig/src/bytes-tape.ts | 8 ++- packages/varsig/src/signing.ts | 4 +- packages/varsig/test/index.test.ts | 0 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 packages/varsig/src/__tests__/hex.util.ts create mode 100644 packages/varsig/src/__tests__/signing.test.ts delete mode 100644 packages/varsig/test/index.test.ts diff --git a/packages/varsig/src/__tests__/hex.util.ts b/packages/varsig/src/__tests__/hex.util.ts new file mode 100644 index 00000000..a098baee --- /dev/null +++ b/packages/varsig/src/__tests__/hex.util.ts @@ -0,0 +1,3 @@ +export function hex(...numbers: Array): Uint8Array { + return new Uint8Array(numbers) +} diff --git a/packages/varsig/src/__tests__/signing.test.ts b/packages/varsig/src/__tests__/signing.test.ts new file mode 100644 index 00000000..52a26489 --- /dev/null +++ b/packages/varsig/src/__tests__/signing.test.ts @@ -0,0 +1,65 @@ +import { test, describe, expect, jest, beforeEach } from '@jest/globals' +import { concat } from 'uint8arrays/concat' +import { SigningDecoder, SigningKind } from '../signing.js' +import { BytesTape } from '../bytes-tape.js' +import * as varintes from 'varintes' +import { hex } from './hex.util.js' +import { randomBytes } from 'node:crypto' + +describe('secp265k1', () => { + describe('recovery bit off', () => { + const bytes = concat([varintes.encode(SigningKind.SECP256K1)[0], hex(0x00)]) + const tape = new BytesTape(bytes) + + beforeEach(() => { + tape.position = 0 + }) + + test('read', () => { + const decoder = new SigningDecoder(tape) + const result = decoder.read() + expect(result.kind).toEqual(SigningKind.SECP256K1) + expect(result.recoveryBit).toEqual(undefined) + }) + test('readSignature', () => { + const decoder = new SigningDecoder(tape) + const result = decoder.read() + const readSpy = jest.spyOn(tape, 'read') + const mockSignature = randomBytes(64) + readSpy.mockImplementation(() => { + return mockSignature + }) + const signature = decoder.readSignature(result) + expect(signature).toEqual(mockSignature) + expect(readSpy).toHaveBeenCalledWith(64) + }) + }) + + describe('recovery bit on', () => { + const bytes = concat([varintes.encode(SigningKind.SECP256K1)[0], hex(27)]) + const tape = new BytesTape(bytes) + + beforeEach(() => { + tape.position = 0 + }) + + test('read', () => { + const decoder = new SigningDecoder(tape) + const result = decoder.read() + expect(result.kind).toEqual(SigningKind.SECP256K1) + expect(result.recoveryBit).toEqual(27) + }) + test('readSignature', () => { + const decoder = new SigningDecoder(tape) + const result = decoder.read() + const readSpy = jest.spyOn(tape, 'read') + const mockSignature = randomBytes(64) + readSpy.mockImplementation(() => { + return mockSignature + }) + const signature = decoder.readSignature(result) + expect(signature).toEqual(mockSignature) + expect(readSpy).toHaveBeenCalledWith(65) + }) + }) +}) diff --git a/packages/varsig/src/bytes-tape.ts b/packages/varsig/src/bytes-tape.ts index ed8ea424..1443375d 100644 --- a/packages/varsig/src/bytes-tape.ts +++ b/packages/varsig/src/bytes-tape.ts @@ -8,14 +8,20 @@ export class BytesTape implements Tape { this.position = 0 } - read(n: number): Uint8Array { + read(n: number, exact = false): Uint8Array { const result = this.input.subarray(this.position, this.position + n) + if (exact && result.byteLength < n) { + throw new Error(`EOF reached while trying to read ${n} bytes`) + } this.position += n return result } readVarint(): T { const bytes = this.read(10) + if (bytes.byteLength === 0) { + throw new Error(`EOF reached while trying to get varint bytes`) + } const [n, bytesRead] = decode(bytes) this.position -= 10 - bytesRead return n as T diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 452a75a7..95d130a5 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -1,4 +1,4 @@ -import { BytesTape } from './bytes-tape.js' +import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' export enum SigningKind { @@ -25,7 +25,7 @@ export class SigningDecoder { switch (signingSigil) { case SigningKind.SECP256K1: { const recoveryBit = this.tape.readVarint() - if (!(recoveryBit === 27 || recoveryBit === 28)) { + if (recoveryBit && !(recoveryBit === 27 || recoveryBit === 28)) { throw new Error(`Wrong recovery bit`) } return { diff --git a/packages/varsig/test/index.test.ts b/packages/varsig/test/index.test.ts deleted file mode 100644 index e69de29b..00000000 From 3fd4ceb7c16406abac94489e5fe890be163dac2a Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 13:40:27 +0300 Subject: [PATCH 045/110] hashing test --- packages/varsig/src/__tests__/hashing.test.ts | 22 +++++++++++++++++++ packages/varsig/src/hashing.ts | 6 ++--- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 packages/varsig/src/__tests__/hashing.test.ts diff --git a/packages/varsig/src/__tests__/hashing.test.ts b/packages/varsig/src/__tests__/hashing.test.ts new file mode 100644 index 00000000..3da3154a --- /dev/null +++ b/packages/varsig/src/__tests__/hashing.test.ts @@ -0,0 +1,22 @@ +import { expect, test } from '@jest/globals' +import { hex } from './hex.util.js' +import { HashingAlgo, HashingDecoder } from '../hashing.js' +import { BytesTape } from '../bytes-tape.js' + +test('SHA2_256', () => { + const tape = new BytesTape(hex(0x12)) + const result = HashingDecoder.read(tape) + expect(result).toEqual(HashingAlgo.SHA2_256) +}) + +test('SHA2_512', () => { + const tape = new BytesTape(hex(0x13)) + const result = HashingDecoder.read(tape) + expect(result).toEqual(HashingAlgo.SHA2_512) +}) + +test('KECCAK256', () => { + const tape = new BytesTape(hex(0x1b)) + const result = HashingDecoder.read(tape) + expect(result).toEqual(HashingAlgo.KECCAK256) +}) diff --git a/packages/varsig/src/hashing.ts b/packages/varsig/src/hashing.ts index 2404a965..a79c775e 100644 --- a/packages/varsig/src/hashing.ts +++ b/packages/varsig/src/hashing.ts @@ -1,4 +1,4 @@ -import { BytesTape } from './bytes-tape.js' +import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' export enum HashingAlgo { @@ -18,9 +18,9 @@ export class HashingDecoder { const hashingSigil = this.tape.readVarint() switch (hashingSigil) { case HashingAlgo.SHA2_512: - throw new Error(`Not implemented: hashingSigil: SHA2_512`) + return HashingAlgo.SHA2_512 case HashingAlgo.SHA2_256: - throw new Error(`Not implemented: hashingSigil: SHA2_256`) + return HashingAlgo.SHA2_256 case HashingAlgo.KECCAK256: return HashingAlgo.KECCAK256 default: From 11502a2167e5253a860868159e8f7cf5ca7f095d Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 13:50:29 +0300 Subject: [PATCH 046/110] wip --- .../varsig/src/__tests__/canonicalization.test.ts | 4 ++++ packages/varsig/src/canonicalization.ts | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 packages/varsig/src/__tests__/canonicalization.test.ts diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts new file mode 100644 index 00000000..49031baa --- /dev/null +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -0,0 +1,4 @@ +import { test } from '@jest/globals' + +test.todo('EIP712') +test.todo('EIP191') diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 67ea2362..0ae07456 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -33,7 +33,7 @@ export class CanonicalizationDecoder { const metadataBytes = this.tape.read(metadataLength) const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) const [types, primaryType, domain] = metadata - const signingInput = (message: any) => { + const fn = (message: any) => { const digestHex = hashTypedData({ domain: decompressDomain(domain as CompressedDomain), message: message, @@ -42,19 +42,19 @@ export class CanonicalizationDecoder { }) return uint8arrays.fromString(digestHex.toLowerCase().replace(/^0x/, ''), 'hex') } - signingInput.kind = CanonicalizationKind.EIP712 - return signingInput + fn.kind = CanonicalizationKind.EIP712 + return fn } case CanonicalizationKind.EIP191: { - const signingInput = (message: string) => { + const fn = (message: string) => { return keccak_256( uint8arrays.fromString( `\x19Ethereum Signed Message:\n` + String(message.length) + message ) ) } - signingInput.kind = CanonicalizationKind.EIP191 - return signingInput + fn.kind = CanonicalizationKind.EIP191 + return fn } default: throw new UnreacheableCaseError(sigil, 'canonicalization kind') From b3a813be36102d4b77862a5f60863641872823cc Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 13:58:43 +0300 Subject: [PATCH 047/110] wip --- .../src/__tests__/canonicalization.test.ts | 25 ++++++++++++++++--- packages/varsig/src/canonicalization.ts | 11 ++++---- packages/varsig/test/parse-eip191.test.ts | 3 ++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index 49031baa..27954de7 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -1,4 +1,23 @@ -import { test } from '@jest/globals' +import { expect, test } from '@jest/globals' +import { BytesTape } from '../bytes-tape.js' +import { CanonicalizationDecoder, CanonicalizationKind } from '../canonicalization.js' +import { concat, toString } from 'uint8arrays' +import { encode } from 'varintes/encode' -test.todo('EIP712') -test.todo('EIP191') +test('EIP712', () => { + const bytes = concat([encode(0xe712)[0]]) + const tape = new BytesTape(bytes) + const result = CanonicalizationDecoder.read(tape) + expect(result.kind).toEqual(CanonicalizationKind.EIP191) + // const canonicalized = toString(result('Hello'), 'hex') + // expect(canonicalized).toEqual('19457468657265756d205369676e6564204d6573736167653a0a3548656c6c6f') +}) + +test('EIP191', () => { + const bytes = concat([encode(0xe191)[0]]) + const tape = new BytesTape(bytes) + const result = CanonicalizationDecoder.read(tape) + expect(result.kind).toEqual(CanonicalizationKind.EIP191) + const canonicalized = toString(result('Hello'), 'hex') + expect(canonicalized).toEqual('19457468657265756d205369676e6564204d6573736167653a0a3548656c6c6f') +}) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 0ae07456..fdffab59 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -1,6 +1,5 @@ import { BytesTape } from './bytes-tape.js' import * as uint8arrays from 'uint8arrays' -import { keccak_256 } from '@noble/hashes/sha3' import { UnreacheableCaseError } from './unreachable-case-error.js' import { hashTypedData } from 'viem' import { CompressedDomain, decompressDomain, decompressTypes } from './encoding/eip712.js' @@ -25,6 +24,10 @@ export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP7 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} + static read(tape: BytesTape) { + return new CanonicalizationDecoder(tape).read() + } + read(): CanonicalizationAlgo { const sigil = this.tape.readVarint() switch (sigil) { @@ -47,10 +50,8 @@ export class CanonicalizationDecoder { } case CanonicalizationKind.EIP191: { const fn = (message: string) => { - return keccak_256( - uint8arrays.fromString( - `\x19Ethereum Signed Message:\n` + String(message.length) + message - ) + return uint8arrays.fromString( + `\x19Ethereum Signed Message:\n` + String(message.length) + message ) } fn.kind = CanonicalizationKind.EIP191 diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 793745e5..d430b082 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -7,6 +7,7 @@ import { BytesTape } from '../src/bytes-tape.js' import { SigningDecoder } from '../src/signing.js' import { HashingDecoder } from '../src/hashing.js' import { CanonicalizationDecoder, CanonicalizationKind } from '../src/canonicalization.js' +import { keccak_256 } from '@noble/hashes/sha3' class Decoder { #tape: BytesTape @@ -70,5 +71,5 @@ test('validate eip191', async () => { if (decoder.signing.recoveryBit) { signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) } - console.log(signature.recoverPublicKey(input).toHex(false)) + console.log(signature.recoverPublicKey(keccak_256(input)).toHex(false)) }) From 059db6e55eb527cab730eee9fda2887de1c27e21 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 14:04:04 +0300 Subject: [PATCH 048/110] Add test vectors for eip712 --- .../varsig/test/__vectors__/eip712-secp256k1.car | Bin 0 -> 6886 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/varsig/test/__vectors__/eip712-secp256k1.car diff --git a/packages/varsig/test/__vectors__/eip712-secp256k1.car b/packages/varsig/test/__vectors__/eip712-secp256k1.car new file mode 100644 index 0000000000000000000000000000000000000000..b1b505deb1a2a7e6f1d94f8a8dd52ba3d373a179 GIT binary patch literal 6886 zcmeHM32+q06=i`Cz=%_2gM{&Bg>6t|*Yw;&C{yeqp(IMcfgo8N?wRS4M%vw3c1A*i z5G)nOk`3mp7(&D;nIjOH!$n~ZQzY0JWH2Dd#KBy)LI@bl4FRjtGqbyrK;?B}Tt!vX zt7>ik{{MGR_xrEs_YPT!!kT92WyeyppKRxt)Z3Yu^;X_S{rsZMYR}Slr=NCS`sqNT z;qv3x-`n+vCH=b={w}W=g>_X6I(Bt3)hBdbp4o6b^GM2zL#sZ#a5*6Z8mhUeG8y{)$TrJ^vK&Ub?)PPm0lw_nJnqwJO6lKH_G*8kr z%>n!sP!vECr3f^X6~qaQAQDI>u)f4ll*|C22nZkmL<)N#X;S7%iUvGK$PxtQg~)Mqqt7~$ljk4ou*Z=*)n$jBYe;Uc%S5;<&854>r{%iru)s~|8b3bG zW#Z3LG6NP%BW9+ip|xyB!!+iqDLAo6#O|g={9)rJbmKnYN&{)GZ;xN>s#}-mg)fdC zse5F~%CerLW()}R0T+fX+|XE>^VXN$8~0Kl_qu)hbnD*`~NXk*3dbxME(|y&6%ltaw;6=)>jdKPwLd=jgwn@c0GP0;jvrK zW!KI-xBHXFo?MW0HOM$lu99VFz!)*dV_G?=$tb2B4_Bxd`c?V*6^q1o4XwU3_M-1q zrDkcu-c1*{6<-M{qu#%{enjf>YZFoqW=BYTg)-d03~N-_K_4X~n*~j-0}-lIbSWPN zpiP1nD^sLxqlS^BhSVr!N4eMTlnoUh4i;IP-dNfe2T@5#4MPJTI-^@vz)p)b35tH( zSQa(SZ<{J%q>Rb*wlWcGZ7q4^*l13_ns4~8oc6O?!`{ciGVv_Qwob#t0ZDM_l-mtS zlJ=33AW;(QWnel>c?l!}h9^CU;khUcud!xQ;>H~H-3qXlF9~Hnx-Ih955OrA*-F!u z#0Vs2NWjnp05rw&f+S)_qzIaY0*RlLVXwhtPjy7!3zTCc5@`>X51X6(WYI#eh(trHF5na7&fQMG786W z*n^A<#RC~oJi@#UD25<709jEIS(+js1sPrf5=oH=DvT`Bq(DN2gSzR3Q%I7*mXf4M z0s$1Bk!e}tc~J(~Gh#ReP>KX-n&e0ka*W8}^9Yg<6Fw=30KwwE$<1j$5+`B``5}gar05)zGxD+4N8lXBky6D zL5l!uv9}#{Cf!n`C5QzZmSILWX&VQSX~EJ~vt?v8>xe}ZmT3`RW+dTe)VWDcX49tj z$iz%?Zy7sS#SE4R$$#7Ez)sQ4$(2=~9W6V|pYPsh8??H-QBrQyj*|0VrRdqhUtCoCc4A#6us^HeQKh(WPlY{+0J&~TpagBOHid@1C~?bUJ9iPhg9;_VZpYvr7jwc}O)-h_=u zN9Bzhb8Xz~KWHe|TvN-oPR$DZyURT%NIaTvd4j|PXygR3(|`}w>b)(}4)%KOdnQVC<_jYy5G$0<+wj}X*xI!o#*20rAgH}4=jOENW zxnHw$RDH-V$+FK%i&p>Q%^v56T}f&niZ-m9Ip_0d?yQ)Qv3bCb2M5jzZ8_@0lA}&F zUsCtos3`b!(1Ln>+k&-4=~IWEs862cpRqrA!&gT$(#rm2*x0<$5_9q3Vq?x26V!`B zAuVhm*;$B6oo3$G0(L!qwzTifg@4T$EY0*R`25YecQ($d-TZES#j@JrWB0x6bv!uO zoo&~4HS&;Ljaabjy?@)zGf$uE#MV@PSYzxtSXWp7%2RA(w{w?ocmK#e?Vqi}E*@MW zu**_)9pAw^ac-pH6spuIszwXg&8b~H@7G(`Z2jc4xU{kJseyO#)p*!!K zgRggZfUtA)Zo83tIBw*AW3nAv->-emI$uFmdD&Ax-v0Hif&)t`HYO!38|19})3OEU zx3vnpcyO^{SJa&C@}ZvJobFDxH%E{6!%MYn+Np&|l3*eJRUr>wqJ>qY&y3yq#k)|9 zDaOkiV~X+KO=61i4vLs!ywHv*#@mdhJ0Dd?QV1_dAqYt@s0CFCUu{mYF76bhhVXAX F{{fq~2tWV; literal 0 HcmV?d00001 From a838a7342e4d0e74cd285e609f862eb833194d6a Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 14:12:50 +0300 Subject: [PATCH 049/110] A bunch of stuff --- packages/varsig/src/encoding/eip712.ts | 14 +- packages/varsig/test/encoding/eip712.test.ts | 129 +++++++++++++------ pnpm-lock.yaml | 17 ++- 3 files changed, 103 insertions(+), 57 deletions(-) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index ae43ae54..1bdd382d 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -214,13 +214,13 @@ function compressTypes(types: Eip712Types): CompressedTypes { // @ts-ignore compressed[key] = value.map(({ name, type }) => [ name, - type // TODO make this more resilient - .replace('uint', 'u') - .replace('int', 'i') - .replace('bytes', 'b') - .replace('string', 's') - .replace('address', 'a') - .replace('bool', 'o'), + type + .replace(/^uint(\d{1,3})$/, 'u$1') + .replace(/^int(\d{1,3})$/, 'i$1') + .replace(/^bytes(\d{0,2})$/, 'b$1') + .replace(/^string$/, 's') + .replace(/^address$/, 'a') + .replace(/^bool$/, 'o'), ]) } return compressed diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts index b3e0e5fb..95ffc223 100644 --- a/packages/varsig/test/encoding/eip712.test.ts +++ b/packages/varsig/test/encoding/eip712.test.ts @@ -3,6 +3,9 @@ import { BytesTape } from '../../src/bytes-tape.ts' import * as uint8arrays from 'uint8arrays' import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' +import * as fs from "node:fs" +import { pipeline } from "node:stream/promises" +import { CARFactory, CarBlock } from "cartonne" const walletClient = createWalletClient({ account: privateKeyToAccount('0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504'), @@ -12,50 +15,20 @@ const walletClient = createWalletClient({ const testData = { "types": { "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } ], "Person": [ - { - "name": "name", - "type": "string" - }, - { - "name": "wallet", - "type": "address" - } + { "name": "name", "type": "string" }, + { "name": "wallet", "type": "address" } ], "Mail": [ - { - "name": "from", - "type": "Person" - }, - { - "name": "to", - "type": "Person" - }, - { - "name": "contents", - "type": "string" - }, - { - "name": "attachment", - "type": "bytes" - } + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" }, + { "name": "attachment", "type": "bytes" } ] }, "primaryType": "Mail", @@ -81,6 +54,43 @@ const testData = { } const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') +const easData = { + "domain": { + "name": "EAS Attestation", + "version": "0.26", + "chainId": 1, + "verifyingContract": "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587" + }, + "primaryType": "Attest", + "message": { + "schema": "0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7", + "recipient": "0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2", + "refUID": "0x0000000000000000000000000000000000000000000000000000000000000000", + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "time": 1699288761, + "revocable": true, + "expirationTime": 0, + "version": 1, + }, + "types": { + "Attest": [ + { "name": "version", "type": "uint16" }, + { "name": "schema", "type": "bytes32" }, + { "name": "recipient", "type": "address" }, + { "name": "time", "type": "uint64" }, + { "name": "expirationTime", "type": "uint64" }, + { "name": "revocable", "type": "bool" }, + { "name": "refUID", "type": "bytes32" }, + { "name": "data", "type": "bytes" } + ] + }, + "signature": { + "v": 27, + "r": "0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7", + "s": "0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e" + } + } + test('Encode eip712 message', async () => { const node = fromEip712(testData) @@ -108,7 +118,44 @@ test('Canonicalize ipld eip712 object', async () => { expect(res1.digest).toEqual(expectedHash) }) -test('Generate test vectors', async () => { +test.only('Generate test vectors', async () => { const signature = await walletClient.signTypedData(testData) console.log('sig', signature) + + function putEntry (car, eip712, node, error) { + const entry = { + valid: error ? false : true, + data: eip712 ? car.put(eip712) : null, + node: node ? car.put(node) : null + } + if (error) entry.error = error + return car.put(entry) + } + + const car = (new CARFactory()).build() + const entries = [] + entries.push(putEntry(car, testData, fromEip712(testData))) + entries.push(putEntry(car, easData, fromEip712(easData))) + // invalid stuff + const invalidData1 = { ...testData, signature: "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b" } + entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) + + const invalidNode1 = fromEip712(testData) + invalidNode1._sig.set([0xec], 1) + entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) + const invalidNode2 = fromEip712(testData) + invalidNode2._sig.set([0x00], 2) + entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) + const invalidNode3 = fromEip712(testData) + invalidNode3._sig.set([0x12], 3) + entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) + + car.put({ + canonicalization: 'eip712', + signature: 'secp256k1', + hash: 'keccak256', + entries + }, { isRoot: true }) + + await pipeline(car, fs.createWriteStream("./eip712-secp256k1.car")); }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1226c9e..6a2cf564 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -582,6 +582,9 @@ importers: '@types/node': specifier: ^20.2.3 version: 20.2.3 + cartonne: + specifier: ^2.2.0 + version: 2.2.0 codeco: specifier: ^1.1.0 version: 1.1.0 @@ -2384,7 +2387,7 @@ packages: ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) await-semaphore: 0.1.3 - cartonne: 2.1.1 + cartonne: 2.2.0 dids: 4.0.1 it-first: 1.0.7 knex: 2.4.2(pg@8.11.0)(sqlite3@5.1.6) @@ -8761,13 +8764,13 @@ packages: resolution: {integrity: sha512-rsJZYVCgXd08sPqwmaIqjAd5SUTfonV0z/gDJ8D6cN8wQphky1kkAYEqQ+hmDxTw7UihvBfjUVUSY+DBEe44jg==} dev: false - /cartonne@2.1.1: - resolution: {integrity: sha512-uUJx6ZBmyNP736OsFy11JNPdXD7gdJkGn7m/y0TDd5+M6W28XUz9CYshO1t5BCf0uw0cYQRmnGc/q34uflN+Jw==} + /cartonne@2.2.0: + resolution: {integrity: sha512-O1rA2AQKnposZJ7oT+GtCQpcv4kfs+gqkCs5rFLBfegP3K0nWNmHj5q4d8NlUxqe3EHvAddCci6WO+ogupl3MA==} dependencies: '@ipld/dag-cbor': 9.0.1 multiformats: 11.0.2 multihashes-sync: 1.1.1 - varintes: 2.0.4 + varintes: 2.0.5 dev: false /catering@2.1.1: @@ -18869,10 +18872,6 @@ packages: /varint@6.0.0: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} - /varintes@2.0.4: - resolution: {integrity: sha512-4Ls542xejhl3tnbUnrZVf/q0ApH3rj7hVRLue2mKDreiXyPFaOP/T0k0elfi+63pcVF18zchT/R/RBAUbnon0A==} - dev: false - /varintes@2.0.5: resolution: {integrity: sha512-iF3jlHLko9NrYjaUZvT3VwypP3V20KNNhT1tzqblyIyrVjNiW7HseGOhuP+apgZBp9X/8+5pxa7kNikhJeZlIw==} dev: false From e7e27a9c80d5dde0920befceae24695e6f8dbfd3 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 14:20:01 +0300 Subject: [PATCH 050/110] wip 0xe712 --- .../src/__tests__/canonicalization.test.ts | 7 ++++--- packages/varsig/src/canonicalization.ts | 16 +++++++++++----- packages/varsig/test/parse-eip191.test.ts | 2 +- packages/varsig/test/parse-eip712.test.ts | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index 27954de7..5febf301 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -3,11 +3,12 @@ import { BytesTape } from '../bytes-tape.js' import { CanonicalizationDecoder, CanonicalizationKind } from '../canonicalization.js' import { concat, toString } from 'uint8arrays' import { encode } from 'varintes/encode' +import { HashingAlgo } from '../hashing.js' test('EIP712', () => { const bytes = concat([encode(0xe712)[0]]) const tape = new BytesTape(bytes) - const result = CanonicalizationDecoder.read(tape) + const result = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256) expect(result.kind).toEqual(CanonicalizationKind.EIP191) // const canonicalized = toString(result('Hello'), 'hex') // expect(canonicalized).toEqual('19457468657265756d205369676e6564204d6573736167653a0a3548656c6c6f') @@ -16,8 +17,8 @@ test('EIP712', () => { test('EIP191', () => { const bytes = concat([encode(0xe191)[0]]) const tape = new BytesTape(bytes) - const result = CanonicalizationDecoder.read(tape) + const result = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256) expect(result.kind).toEqual(CanonicalizationKind.EIP191) const canonicalized = toString(result('Hello'), 'hex') - expect(canonicalized).toEqual('19457468657265756d205369676e6564204d6573736167653a0a3548656c6c6f') + expect(canonicalized).toEqual('aa744ba2ca576ec62ca0045eca00ad3917fdf7ffa34fbbae50828a5a69c1580e') }) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index fdffab59..31be4071 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -3,6 +3,8 @@ import * as uint8arrays from 'uint8arrays' import { UnreacheableCaseError } from './unreachable-case-error.js' import { hashTypedData } from 'viem' import { CompressedDomain, decompressDomain, decompressTypes } from './encoding/eip712.js' +import { HashingAlgo } from './hashing' +import { keccak_256 } from '@noble/hashes/sha3' export enum CanonicalizationKind { EIP712 = 0xe712, @@ -24,14 +26,15 @@ export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP7 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - static read(tape: BytesTape) { - return new CanonicalizationDecoder(tape).read() + static read(tape: BytesTape, hashing: HashingAlgo) { + return new CanonicalizationDecoder(tape).read(hashing) } - read(): CanonicalizationAlgo { + read(hashing: HashingAlgo): CanonicalizationAlgo { const sigil = this.tape.readVarint() switch (sigil) { case CanonicalizationKind.EIP712: { + if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP712 mandates use of KECCAK 256`) const metadataLength = this.tape.readVarint() const metadataBytes = this.tape.read(metadataLength) const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) @@ -49,9 +52,12 @@ export class CanonicalizationDecoder { return fn } case CanonicalizationKind.EIP191: { + if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) const fn = (message: string) => { - return uint8arrays.fromString( - `\x19Ethereum Signed Message:\n` + String(message.length) + message + return keccak_256( + uint8arrays.fromString( + `\x19Ethereum Signed Message:\n` + String(message.length) + message + ) ) } fn.kind = CanonicalizationKind.EIP191 diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index d430b082..39cec63c 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -21,7 +21,7 @@ class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read() + const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing) const signature = signingDecoder.readSignature(signing) return { signing: signing, diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index fd830f56..a41b55e5 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -107,7 +107,7 @@ class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read() + const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing) const signature = signingDecoder.readSignature(signing) return { signing: signing, From 9b18fb500526c01916d4892f26755c291e67e3ce Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 14:39:48 +0300 Subject: [PATCH 051/110] Main entrypoint --- packages/varsig/src/varsig.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 packages/varsig/src/varsig.ts diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts new file mode 100644 index 00000000..d62eec33 --- /dev/null +++ b/packages/varsig/src/varsig.ts @@ -0,0 +1,14 @@ + +interface VarsigNode { + _sig: Uint8Array + [key: string]: any +} + +type EthAddress = `0x${string}` +type PublicKey = Uint8Array + +type Decoded = any + +export function verify (node: VarsigNode, verificationKey: PublicKey | EthAddress): Decoded { + +} From 765e5e317117d1f45c2131a8fc2c5cc8d9b7e7d1 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 14:44:36 +0300 Subject: [PATCH 052/110] test 712 can --- .../src/__tests__/canonicalization.test.ts | 89 +++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index 5febf301..573a3047 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -4,14 +4,93 @@ import { CanonicalizationDecoder, CanonicalizationKind } from '../canonicalizati import { concat, toString } from 'uint8arrays' import { encode } from 'varintes/encode' import { HashingAlgo } from '../hashing.js' +import { fromEip712A } from '../encoding/eip712.js' + +const TEST_DATA = { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + Person: [ + { + name: 'name', + type: 'string', + }, + { + name: 'wallet', + type: 'address', + }, + ], + Mail: [ + { + name: 'from', + type: 'Person', + }, + { + name: 'to', + type: 'Person', + }, + { + name: 'contents', + type: 'string', + }, + { + name: 'attachment', + type: 'bytes', + }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + attachment: '0xababababababababababa83459873459873459873498575986734359', + }, +} as const test('EIP712', () => { - const bytes = concat([encode(0xe712)[0]]) + const a = fromEip712A({ + // @ts-ignore + types: TEST_DATA.types, + domain: TEST_DATA.domain, + primaryType: TEST_DATA.primaryType, + message: TEST_DATA.message, + }) + const bytes = concat([encode(0xe712)[0], a.params]) const tape = new BytesTape(bytes) - const result = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256) - expect(result.kind).toEqual(CanonicalizationKind.EIP191) - // const canonicalized = toString(result('Hello'), 'hex') - // expect(canonicalized).toEqual('19457468657265756d205369676e6564204d6573736167653a0a3548656c6c6f') + const canonicalization = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256) + expect(canonicalization.kind).toEqual(CanonicalizationKind.EIP712) + if (canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error() + const input = toString(canonicalization(TEST_DATA.message), 'hex') + expect(input).toEqual('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4') }) test('EIP191', () => { From e62f9f8f693983c78ab70a5f60b3e0adc2f6ac45 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 14:55:58 +0300 Subject: [PATCH 053/110] decoder --- packages/varsig/src/decoder.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/varsig/src/decoder.ts diff --git a/packages/varsig/src/decoder.ts b/packages/varsig/src/decoder.ts new file mode 100644 index 00000000..07c1d576 --- /dev/null +++ b/packages/varsig/src/decoder.ts @@ -0,0 +1,33 @@ +import type { BytesTape } from './bytes-tape.js' +import { SigningDecoder } from './signing.js' +import { HashingDecoder } from './hashing.js' +import { CanonicalizationDecoder } from './canonicalization.js' + +export class Decoder { + #tape: BytesTape + + constructor(tape: BytesTape) { + this.#tape = tape + } + + read() { + this.readVarsigSigil() + const signingDecoder = new SigningDecoder(this.#tape) + const signing = signingDecoder.read() + const hashing = HashingDecoder.read(this.#tape) + const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing) + const signature = signingDecoder.readSignature(signing) + return { + signing: signing, + hashing: hashing, + canonicalization: canonicalization, + signature: signature, + } + } + + readVarsigSigil() { + const sigil = this.#tape.readVarint() + if (sigil !== 0x34) throw new Error(`Not a varsig`) + return sigil + } +} From c94fdaff97d34b02bc026cb98fd817e88277a11f Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 15:00:22 +0300 Subject: [PATCH 054/110] decoder --- packages/varsig/src/decoder.ts | 15 ++++++--- packages/varsig/test/parse-eip191.test.ts | 38 ++--------------------- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/packages/varsig/src/decoder.ts b/packages/varsig/src/decoder.ts index 07c1d576..65eaaa96 100644 --- a/packages/varsig/src/decoder.ts +++ b/packages/varsig/src/decoder.ts @@ -1,7 +1,14 @@ import type { BytesTape } from './bytes-tape.js' -import { SigningDecoder } from './signing.js' -import { HashingDecoder } from './hashing.js' -import { CanonicalizationDecoder } from './canonicalization.js' +import { SigningDecoder, type SigningAlgo } from './signing.js' +import { HashingDecoder, type HashingAlgo } from './hashing.js' +import { CanonicalizationDecoder, type CanonicalizationAlgo } from './canonicalization.js' + +export type Varsig = { + signing: SigningAlgo + hashing: HashingAlgo + canonicalization: CanonicalizationAlgo + signature: Uint8Array +} export class Decoder { #tape: BytesTape @@ -10,7 +17,7 @@ export class Decoder { this.#tape = tape } - read() { + read(): Varsig { this.readVarsigSigil() const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 39cec63c..e8f8baa5 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -4,43 +4,9 @@ import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' -import { SigningDecoder } from '../src/signing.js' -import { HashingDecoder } from '../src/hashing.js' -import { CanonicalizationDecoder, CanonicalizationKind } from '../src/canonicalization.js' +import { CanonicalizationKind } from '../src/canonicalization.js' import { keccak_256 } from '@noble/hashes/sha3' - -class Decoder { - #tape: BytesTape - - constructor(tape: BytesTape) { - this.#tape = tape - } - - read() { - this.readVarsigSigil() - const signingDecoder = new SigningDecoder(this.#tape) - const signing = signingDecoder.read() - const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing) - const signature = signingDecoder.readSignature(signing) - return { - signing: signing, - hashing: hashing, - canonicalization: canonicalization, - signature: signature, - } - } - - readVarsigSigil() { - const sigil = this.#tape.readVarint() - if (sigil !== 0x34) throw new Error(`Not a varsig`) - return sigil - } -} - -function eip191canonicalization(message: string) { - return uint8arrays.fromString(message) -} +import { Decoder } from '../src/decoder.js' function hex(...numbers: Array): Uint8Array { return new Uint8Array(numbers) From cc1008b5a62c9c3600f9536c6d8e725114cd50cc Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 15:02:39 +0300 Subject: [PATCH 055/110] cleanup --- packages/varsig/test/parse-eip191.test.ts | 2 +- packages/varsig/test/parse-eip712.test.ts | 55 ++--------------------- 2 files changed, 4 insertions(+), 53 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index e8f8baa5..85197f44 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -37,5 +37,5 @@ test('validate eip191', async () => { if (decoder.signing.recoveryBit) { signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) } - console.log(signature.recoverPublicKey(keccak_256(input)).toHex(false)) + console.log(signature.recoverPublicKey(input).toHex(false)) }) diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index a41b55e5..273f1fd9 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -4,14 +4,10 @@ import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' -import { SigningAlgo, SigningDecoder } from '../src/signing.js' -import { HashingAlgo, HashingDecoder } from '../src/hashing.js' -import { - CanonicalizationAlgo, - CanonicalizationDecoder, - CanonicalizationKind, -} from '../src/canonicalization.js' +import { CanonicalizationKind } from '../src/canonicalization.js' import { fromEip712A } from '../src/encoding/eip712' +import { Decoder } from '../src/decoder.js' +import { hex } from '../src/__tests__/hex.util.js' const testData = { types: { @@ -83,51 +79,6 @@ const testData = { }, } as const -const expectedHash = uint8arrays.fromString( - '703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', - 'base16' -) - -type DecodedVarsig = { - signing: SigningAlgo - hashing: HashingAlgo - canonicalization: CanonicalizationAlgo - signature: Uint8Array -} - -class Decoder { - #tape: BytesTape - - constructor(tape: BytesTape) { - this.#tape = tape - } - - read(): DecodedVarsig { - this.readVarsigSigil() - const signingDecoder = new SigningDecoder(this.#tape) - const signing = signingDecoder.read() - const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing) - const signature = signingDecoder.readSignature(signing) - return { - signing: signing, - hashing: hashing, - canonicalization: canonicalization, - signature: signature, - } - } - - readVarsigSigil() { - const sigil = this.#tape.readVarint() - if (sigil !== 0x34) throw new Error(`Not a varsig`) - return sigil - } -} - -function hex(...numbers: Array): Uint8Array { - return new Uint8Array(numbers) -} - test('712 flow', async () => { const account = privateKeyToAccount( '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' From 1e7eb1c2e2e492afcd6fb8eb0068e2d6eaa66a2c Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 15:03:00 +0300 Subject: [PATCH 056/110] cleanup --- packages/varsig/test/parse-eip191.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 85197f44..59112ac5 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -5,12 +5,8 @@ import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' import { CanonicalizationKind } from '../src/canonicalization.js' -import { keccak_256 } from '@noble/hashes/sha3' import { Decoder } from '../src/decoder.js' - -function hex(...numbers: Array): Uint8Array { - return new Uint8Array(numbers) -} +import { hex } from '../src/__tests__/hex.util.js' test('validate eip191', async () => { const account = privateKeyToAccount( From 04ade47df949f6cead1b63065981cca042dd87cb Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 15:21:42 +0300 Subject: [PATCH 057/110] cleanup --- packages/varsig/src/canonicalization.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 31be4071..c71af9c5 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -3,7 +3,7 @@ import * as uint8arrays from 'uint8arrays' import { UnreacheableCaseError } from './unreachable-case-error.js' import { hashTypedData } from 'viem' import { CompressedDomain, decompressDomain, decompressTypes } from './encoding/eip712.js' -import { HashingAlgo } from './hashing' +import { HashingAlgo } from './hashing.js' import { keccak_256 } from '@noble/hashes/sha3' export enum CanonicalizationKind { From 97a6cd10424a75fa19fe19106c6ce86714eae7ae Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 15:25:01 +0300 Subject: [PATCH 058/110] Top level API --- packages/varsig/src/decoder.ts | 2 +- packages/varsig/src/signing.ts | 12 ++++++++++-- packages/varsig/src/varsig.ts | 18 +++++++++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/varsig/src/decoder.ts b/packages/varsig/src/decoder.ts index 65eaaa96..21e59d7b 100644 --- a/packages/varsig/src/decoder.ts +++ b/packages/varsig/src/decoder.ts @@ -22,7 +22,7 @@ export class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing) + const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing, signing) const signature = signingDecoder.readSignature(signing) return { signing: signing, diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 95d130a5..65d2e95a 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -1,6 +1,10 @@ import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' +type EthAddress = `0x${string}` +type PublicKey = Uint8Array +type VerificationKey = PublicKey | EthAddress + export enum SigningKind { RSA = 0x1205, SECP256K1 = 0xe7, @@ -8,7 +12,11 @@ export enum SigningKind { export type SigningSecp256k1 = { kind: SigningKind.SECP256K1 - recoveryBit: number | undefined + verify: ( + signature: Uint8Array, + verificationKey: VerificationKey, + digest: Uint8Array + ): Promise } export type SigningAlgo = SigningSecp256k1 @@ -30,7 +38,7 @@ export class SigningDecoder { } return { kind: SigningKind.SECP256K1, - recoveryBit: recoveryBit || undefined, + verify: async () => Promise.resolve(false) } } case SigningKind.RSA: diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index d62eec33..a794aa88 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -9,6 +9,22 @@ type PublicKey = Uint8Array type Decoded = any -export function verify (node: VarsigNode, verificationKey: PublicKey | EthAddress): Decoded { +interface VerificationResult { + valid: boolean + decoded: Decoded +} + +export async function verify ( + node: VarsigNode, + verificationKey: PublicKey | EthAddress +): VerificationResult { + const { canoncalize, signing, signature } = (new Decoder(node._sig)).read() + + delete node._sig + const { digest, decoded } = canoncalize(node) + return { + decoded, + valid: await signing.verify(signature, verificationKey, digest) + } } From f9266b5d13536a1abd43271831d95d2450a4c491 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 17:26:15 +0300 Subject: [PATCH 059/110] Full flow almost --- packages/varsig/src/canonicalization.ts | 8 +++++--- packages/varsig/src/varsig.ts | 21 ++++++++++----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index c71af9c5..e5fd3f42 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -16,9 +16,10 @@ type CanonicalizationEIP191 = { (message: string): Uint8Array } +type SignatureInput = Uint8Array type CanonicalizationEIP712 = { kind: CanonicalizationKind.EIP712 - (message: any): Uint8Array + (message: any): SignatureInput } export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP712 @@ -40,12 +41,13 @@ export class CanonicalizationDecoder { const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) const [types, primaryType, domain] = metadata const fn = (message: any) => { - const digestHex = hashTypedData({ + const decoded = { domain: decompressDomain(domain as CompressedDomain), message: message, primaryType: primaryType, types: decompressTypes(types), - }) + } + const digestHex = hashTypedData(decoded) return uint8arrays.fromString(digestHex.toLowerCase().replace(/^0x/, ''), 'hex') } fn.kind = CanonicalizationKind.EIP712 diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index a794aa88..c7986303 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -9,22 +9,21 @@ type PublicKey = Uint8Array type Decoded = any -interface VerificationResult { - valid: boolean - decoded: Decoded -} - export async function verify ( node: VarsigNode, verificationKey: PublicKey | EthAddress -): VerificationResult { - const { canoncalize, signing, signature } = (new Decoder(node._sig)).read() +): Promise { + const { canonicalization, signing, signature } = (new Decoder(node._sig)).read() delete node._sig - const { digest, decoded } = canoncalize(node) + const signatureInput = canonicalization(node) - return { - decoded, - valid: await signing.verify(signature, verificationKey, digest) + return signing.verify(signatureInput, signature, verificationKey) } } + +// export async function toOriginal (node: VarsigNode): Promise { +// const { canonicalization } = (new Decoder(node._sig)).read() +// delete node._sig +// // return canonicalization(node) +// } From 0e8ddd344ead564ce753f05b2f360ef7c558a8bc Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 17:40:27 +0300 Subject: [PATCH 060/110] Implement signature verifier --- packages/varsig/src/signing.ts | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 65d2e95a..f5d46e4e 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -12,13 +12,15 @@ export enum SigningKind { export type SigningSecp256k1 = { kind: SigningKind.SECP256K1 - verify: ( - signature: Uint8Array, - verificationKey: VerificationKey, - digest: Uint8Array - ): Promise + verify: VerifySignatureFn } +export type VerifySignatureFn = ( + input: Uint8Array, + signature: Uint8Array, + verificationKey: VerificationKey +) => Promise + export type SigningAlgo = SigningSecp256k1 export class SigningDecoder { @@ -38,7 +40,22 @@ export class SigningDecoder { } return { kind: SigningKind.SECP256K1, - verify: async () => Promise.resolve(false) + verify: async (input, signature, verificationKey) => { + let k1Sig = secp256k1.Signature.fromCompact(decoder.signature) + if (recoveryBit) { + k1Sig = k1Sig.addRecoveryBit(recoveryBit - 27) + } + const recoveredKey = k1Sig.recoverPublicKey(input) + // compare recoveredKey with verificationKey + if (verificationKey instanceof Uint8Array) { + return recoveredKey.toBytes().equals(verificationKey) + } + else if (typeof verificationKey === 'string') { + // convert recoveredKey to eth address + const recoveredAddress = '0x' + recoveredKey.toBytes().slice(-20).toString('hex') + return recoveredAddress === verificationKey + } + } } } case SigningKind.RSA: From 6219181f1731e468f966414808c0e7d102d4bb4d Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 18:44:30 +0300 Subject: [PATCH 061/110] Make more test pass --- .../src/__tests__/canonicalization.test.ts | 7 +- packages/varsig/src/__tests__/signing.test.ts | 4 +- packages/varsig/src/canonicalization.ts | 50 +++--- .../varsig/src/{encoding => canons}/eip712.ts | 64 +++---- .../varsig/src/{encoding => canons}/jws.ts | 0 packages/varsig/src/decoder.ts | 2 +- packages/varsig/src/signing.ts | 8 +- packages/varsig/test/canons/eip712.test.ts | 161 ++++++++++++++++++ packages/varsig/test/encoding/eip712.test.ts | 161 ------------------ 9 files changed, 231 insertions(+), 226 deletions(-) rename packages/varsig/src/{encoding => canons}/eip712.ts (82%) rename packages/varsig/src/{encoding => canons}/jws.ts (100%) create mode 100644 packages/varsig/test/canons/eip712.test.ts delete mode 100644 packages/varsig/test/encoding/eip712.test.ts diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index 573a3047..51aeeb05 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -4,7 +4,8 @@ import { CanonicalizationDecoder, CanonicalizationKind } from '../canonicalizati import { concat, toString } from 'uint8arrays' import { encode } from 'varintes/encode' import { HashingAlgo } from '../hashing.js' -import { fromEip712A } from '../encoding/eip712.js' +import { SigningKind } from '../signing.js' +import { fromEip712A } from '../canons/eip712.js' const TEST_DATA = { types: { @@ -86,7 +87,7 @@ test('EIP712', () => { }) const bytes = concat([encode(0xe712)[0], a.params]) const tape = new BytesTape(bytes) - const canonicalization = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256) + const canonicalization = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256, SigningKind.SECP256K1) expect(canonicalization.kind).toEqual(CanonicalizationKind.EIP712) if (canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error() const input = toString(canonicalization(TEST_DATA.message), 'hex') @@ -96,7 +97,7 @@ test('EIP712', () => { test('EIP191', () => { const bytes = concat([encode(0xe191)[0]]) const tape = new BytesTape(bytes) - const result = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256) + const result = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256, SigningKind.SECP256K1) expect(result.kind).toEqual(CanonicalizationKind.EIP191) const canonicalized = toString(result('Hello'), 'hex') expect(canonicalized).toEqual('aa744ba2ca576ec62ca0045eca00ad3917fdf7ffa34fbbae50828a5a69c1580e') diff --git a/packages/varsig/src/__tests__/signing.test.ts b/packages/varsig/src/__tests__/signing.test.ts index 52a26489..3e749946 100644 --- a/packages/varsig/src/__tests__/signing.test.ts +++ b/packages/varsig/src/__tests__/signing.test.ts @@ -47,7 +47,7 @@ describe('secp265k1', () => { const decoder = new SigningDecoder(tape) const result = decoder.read() expect(result.kind).toEqual(SigningKind.SECP256K1) - expect(result.recoveryBit).toEqual(27) + // expect(result.recoveryBit).toEqual(27) }) test('readSignature', () => { const decoder = new SigningDecoder(tape) @@ -59,7 +59,7 @@ describe('secp265k1', () => { }) const signature = decoder.readSignature(result) expect(signature).toEqual(mockSignature) - expect(readSpy).toHaveBeenCalledWith(65) + expect(readSpy).toHaveBeenCalledWith(64) }) }) }) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index e5fd3f42..c156e034 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -2,12 +2,12 @@ import { BytesTape } from './bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { UnreacheableCaseError } from './unreachable-case-error.js' import { hashTypedData } from 'viem' -import { CompressedDomain, decompressDomain, decompressTypes } from './encoding/eip712.js' +import { Eip712 } from './canons/eip712.js' import { HashingAlgo } from './hashing.js' import { keccak_256 } from '@noble/hashes/sha3' export enum CanonicalizationKind { - EIP712 = 0xe712, + EIP712 = Eip712.SIGIL, EIP191 = 0xe191, } @@ -27,32 +27,34 @@ export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP7 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - static read(tape: BytesTape, hashing: HashingAlgo) { - return new CanonicalizationDecoder(tape).read(hashing) + static read(tape: BytesTape, hashing: HashingAlgo, sigKind: SigningKind) { + return new CanonicalizationDecoder(tape).read(hashing, sigKind) } - read(hashing: HashingAlgo): CanonicalizationAlgo { + read(hashing: HashingAlgo, sigKind: SigningKind): CanonicalizationAlgo { const sigil = this.tape.readVarint() switch (sigil) { - case CanonicalizationKind.EIP712: { - if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP712 mandates use of KECCAK 256`) - const metadataLength = this.tape.readVarint() - const metadataBytes = this.tape.read(metadataLength) - const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) - const [types, primaryType, domain] = metadata - const fn = (message: any) => { - const decoded = { - domain: decompressDomain(domain as CompressedDomain), - message: message, - primaryType: primaryType, - types: decompressTypes(types), - } - const digestHex = hashTypedData(decoded) - return uint8arrays.fromString(digestHex.toLowerCase().replace(/^0x/, ''), 'hex') - } - fn.kind = CanonicalizationKind.EIP712 - return fn - } + case Eip712.SIGIL: + return Eip712.prepareCanonicalization(this.tape, hashing, sigKind) + // case CanonicalizationKind.EIP712: { + // if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP712 mandates use of KECCAK 256`) + // const metadataLength = this.tape.readVarint() + // const metadataBytes = this.tape.read(metadataLength) + // const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) + // const [types, primaryType, domain] = metadata + // const fn = (message: any) => { + // const decoded = { + // domain: decompressDomain(domain as CompressedDomain), + // message: message, + // primaryType: primaryType, + // types: decompressTypes(types), + // } + // const digestHex = hashTypedData(decoded) + // return uint8arrays.fromString(digestHex.toLowerCase().replace(/^0x/, ''), 'hex') + // } + // fn.kind = CanonicalizationKind.EIP712 + // return fn + // } case CanonicalizationKind.EIP191: { if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) const fn = (message: string) => { diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/canons/eip712.ts similarity index 82% rename from packages/varsig/src/encoding/eip712.ts rename to packages/varsig/src/canons/eip712.ts index 1bdd382d..f02636bd 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -1,3 +1,5 @@ +import type { BytesTape } from '../bytes-tape.js' +import type { HashingAlgo } from '../hashing.js' import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' import { hashTypedData, Hex, TypedDataDomain } from 'viem' @@ -40,7 +42,7 @@ interface CanonicalizerResult { digest: Uint8Array decoded: any } -type Canonicalize = (node: IpldNode) => CanonicalizerResult +type Canonicalization = (node: IpldNode) => Uint8Array interface CanonicalizerSetup { remainder: Uint8Array @@ -49,46 +51,50 @@ interface CanonicalizerSetup { const SUPPORTED_KEY_TYPES = [ 0xe7, // secp256k1 - 0x1271, // eip1271 contract signature + // 0x1271, // eip1271 contract signature ] const SUPPORTED_HASH_TYPE = 0x1b // keccak256 -export const CODEC = 0xe712 // TODO encode as varint - -export function setupCanonicalizer( - varsigReminder: Uint8Array, - // @ts-ignore - hasher: (data: Uint8Array) => Uint8Array, - hashType: number, - keyType: number -): CanonicalizerSetup { - const [metadataLength, read] = varintes.decode(varsigReminder) - const metadataBytes = varsigReminder.subarray(read, read + metadataLength) +const SIGIL = 0xe712 + +export function prepareCanonicalization( + tape: BytesTape, + hashType: HashingAlgo, + keyType: SignatureKind +): Canonicalization { + if (hashType !== SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashType}`) + if (!SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) + const metadataLength = tape.readVarint() + const metadataBytes = tape.read(metadataLength) const [types, primaryType, domain] = JSON.parse(uint8arrays.toString(metadataBytes)) - if (SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) - if (hashType === SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashType}`) const metadata = { types: decompressTypes(types), primaryType, domain: decompressDomain(domain), } - return { - remainder: varsigReminder.subarray(read + metadataLength), - canonicalize: parameterizeCanonicalizer(metadata), - } -} - -function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Canonicalize { - return (node: IpldNode) => { + const fn = (node: IpldNode) => { const message = ipldNodeToMessage(node) // @ts-ignore - const hexHash = hashTypedData({ types, primaryType, domain, message }) - return { - digest: uint8arrays.fromString(hexHash.slice(2), 'base16'), - decoded: { types, primaryType, domain, message }, - } + const hexHash = hashTypedData({ ...metadata, message }) + return uint8arrays.fromString(hexHash.slice(2), 'base16') } -} + fn.kind = SIGIL + return fn +} + +export const Eip712 = { SIGIL, prepareCanonicalization } + +// function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Canonicalize { +// return (node: IpldNode) => { +// const message = ipldNodeToMessage(node) +// // @ts-ignore +// const hexHash = hashTypedData({ types, primaryType, domain, message }) +// return { +// digest: uint8arrays.fromString(hexHash.slice(2), 'base16'), +// decoded: { types, primaryType, domain, message }, +// } +// } +// } function ipldNodeToMessage(node: IpldNode): Record { const message = {} diff --git a/packages/varsig/src/encoding/jws.ts b/packages/varsig/src/canons/jws.ts similarity index 100% rename from packages/varsig/src/encoding/jws.ts rename to packages/varsig/src/canons/jws.ts diff --git a/packages/varsig/src/decoder.ts b/packages/varsig/src/decoder.ts index 21e59d7b..a4da8065 100644 --- a/packages/varsig/src/decoder.ts +++ b/packages/varsig/src/decoder.ts @@ -22,7 +22,7 @@ export class Decoder { const signingDecoder = new SigningDecoder(this.#tape) const signing = signingDecoder.read() const hashing = HashingDecoder.read(this.#tape) - const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing, signing) + const canonicalization = new CanonicalizationDecoder(this.#tape).read(hashing, signing.kind) const signature = signingDecoder.readSignature(signing) return { signing: signing, diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index f5d46e4e..e06d06d8 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -34,7 +34,7 @@ export class SigningDecoder { const signingSigil = this.tape.readVarint() switch (signingSigil) { case SigningKind.SECP256K1: { - const recoveryBit = this.tape.readVarint() + const recoveryBit = this.tape.read(1)[0] if (recoveryBit && !(recoveryBit === 27 || recoveryBit === 28)) { throw new Error(`Wrong recovery bit`) } @@ -68,11 +68,7 @@ export class SigningDecoder { readSignature(signing: SigningAlgo): Uint8Array { switch (signing.kind) { case SigningKind.SECP256K1: { - if (signing.recoveryBit) { - return this.tape.read(65) - } else { - return this.tape.read(64) - } + return this.tape.read(64) } default: throw new UnreacheableCaseError(signing.kind, 'signing kind') diff --git a/packages/varsig/test/canons/eip712.test.ts b/packages/varsig/test/canons/eip712.test.ts new file mode 100644 index 00000000..382fbbe0 --- /dev/null +++ b/packages/varsig/test/canons/eip712.test.ts @@ -0,0 +1,161 @@ +// import { fromEip712, setupCanonicalizer } from '../../src/canons/eip712.ts' +// import { BytesTape } from '../../src/bytes-tape.ts' +// import * as uint8arrays from 'uint8arrays' +// import { createWalletClient, custom } from 'viem' +// import { privateKeyToAccount } from 'viem/accounts' +// import * as fs from "node:fs" +// import { pipeline } from "node:stream/promises" +// import { CARFactory, CarBlock } from "cartonne" +// +// const walletClient = createWalletClient({ +// account: privateKeyToAccount('0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504'), +// transport: custom({ request: async () => {}}) +// }) +// +// const testData = { +// "types": { +// "EIP712Domain": [ +// { "name": "name", "type": "string" }, +// { "name": "version", "type": "string" }, +// { "name": "chainId", "type": "uint256" }, +// { "name": "verifyingContract", "type": "address" } +// ], +// "Person": [ +// { "name": "name", "type": "string" }, +// { "name": "wallet", "type": "address" } +// ], +// "Mail": [ +// { "name": "from", "type": "Person" }, +// { "name": "to", "type": "Person" }, +// { "name": "contents", "type": "string" }, +// { "name": "attachment", "type": "bytes" } +// ] +// }, +// "primaryType": "Mail", +// "domain": { +// "name": "Ether Mail", +// "version": "1", +// "chainId": 1, +// "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" +// }, +// "message": { +// "from": { +// "name": "Cow", +// "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" +// }, +// "to": { +// "name": "Bob", +// "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" +// }, +// "contents": "Hello, Bob!", +// "attachment": "0xababababababababababa83459873459873459873498575986734359" +// }, +// "signature": "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b" +// } +// const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') +// +// const easData = { +// "domain": { +// "name": "EAS Attestation", +// "version": "0.26", +// "chainId": 1, +// "verifyingContract": "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587" +// }, +// "primaryType": "Attest", +// "message": { +// "schema": "0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7", +// "recipient": "0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2", +// "refUID": "0x0000000000000000000000000000000000000000000000000000000000000000", +// "data": "0x0000000000000000000000000000000000000000000000000000000000000001", +// "time": 1699288761, +// "revocable": true, +// "expirationTime": 0, +// "version": 1, +// }, +// "types": { +// "Attest": [ +// { "name": "version", "type": "uint16" }, +// { "name": "schema", "type": "bytes32" }, +// { "name": "recipient", "type": "address" }, +// { "name": "time", "type": "uint64" }, +// { "name": "expirationTime", "type": "uint64" }, +// { "name": "revocable", "type": "bool" }, +// { "name": "refUID", "type": "bytes32" }, +// { "name": "data", "type": "bytes" } +// ] +// }, +// "signature": { +// "v": 27, +// "r": "0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7", +// "s": "0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e" +// } +// } +// +// +// test('Encode eip712 message', async () => { +// const node = fromEip712(testData) +// +// expect(node._sig.length).toEqual(268) +// expect(node.attachment instanceof Uint8Array).toBeTruthy() +// console.log(node) +// }) +// +// +// test('Canonicalize ipld eip712 object', async () => { +// const node = fromEip712(testData) +// const tape = new BytesTape(node._sig) +// tape.readVarint() // skip sigil +// tape.readVarint() // skip key type +// tape.read(1) // skip recovery bit +// tape.readVarint() // skip hash type +// tape.readVarint() // skip canonicalizer codec +// const can1 = setupCanonicalizer(tape.remainder) +// expect(can1.remainder.length).toEqual(64) +// delete node._sig +// delete testData.signature +// const res1 = can1.canonicalize(node) +// expect(res1.decoded).toEqual(testData) +// expect(res1.digest).toEqual(expectedHash) +// }) +// +// test.only('Generate test vectors', async () => { +// const signature = await walletClient.signTypedData(testData) +// console.log('sig', signature) +// +// function putEntry (car, eip712, node, error) { +// const entry = { +// valid: error ? false : true, +// data: eip712 ? car.put(eip712) : null, +// node: node ? car.put(node) : null +// } +// if (error) entry.error = error +// return car.put(entry) +// } +// +// const car = (new CARFactory()).build() +// const entries = [] +// entries.push(putEntry(car, testData, fromEip712(testData))) +// entries.push(putEntry(car, easData, fromEip712(easData))) +// // invalid stuff +// const invalidData1 = { ...testData, signature: "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b" } +// entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) +// +// const invalidNode1 = fromEip712(testData) +// invalidNode1._sig.set([0xec], 1) +// entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) +// const invalidNode2 = fromEip712(testData) +// invalidNode2._sig.set([0x00], 2) +// entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) +// const invalidNode3 = fromEip712(testData) +// invalidNode3._sig.set([0x12], 3) +// entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) +// +// car.put({ +// canonicalization: 'eip712', +// signature: 'secp256k1', +// hash: 'keccak256', +// entries +// }, { isRoot: true }) +// +// await pipeline(car, fs.createWriteStream("./eip712-secp256k1.car")); +// }) diff --git a/packages/varsig/test/encoding/eip712.test.ts b/packages/varsig/test/encoding/eip712.test.ts deleted file mode 100644 index 95ffc223..00000000 --- a/packages/varsig/test/encoding/eip712.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { fromEip712, setupCanonicalizer } from '../../src/encoding/eip712.ts' -import { BytesTape } from '../../src/bytes-tape.ts' -import * as uint8arrays from 'uint8arrays' -import { createWalletClient, custom } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import * as fs from "node:fs" -import { pipeline } from "node:stream/promises" -import { CARFactory, CarBlock } from "cartonne" - -const walletClient = createWalletClient({ - account: privateKeyToAccount('0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504'), - transport: custom({ request: async () => {}}) -}) - -const testData = { - "types": { - "EIP712Domain": [ - { "name": "name", "type": "string" }, - { "name": "version", "type": "string" }, - { "name": "chainId", "type": "uint256" }, - { "name": "verifyingContract", "type": "address" } - ], - "Person": [ - { "name": "name", "type": "string" }, - { "name": "wallet", "type": "address" } - ], - "Mail": [ - { "name": "from", "type": "Person" }, - { "name": "to", "type": "Person" }, - { "name": "contents", "type": "string" }, - { "name": "attachment", "type": "bytes" } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!", - "attachment": "0xababababababababababa83459873459873459873498575986734359" - }, - "signature": "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b" -} -const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') - -const easData = { - "domain": { - "name": "EAS Attestation", - "version": "0.26", - "chainId": 1, - "verifyingContract": "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587" - }, - "primaryType": "Attest", - "message": { - "schema": "0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7", - "recipient": "0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2", - "refUID": "0x0000000000000000000000000000000000000000000000000000000000000000", - "data": "0x0000000000000000000000000000000000000000000000000000000000000001", - "time": 1699288761, - "revocable": true, - "expirationTime": 0, - "version": 1, - }, - "types": { - "Attest": [ - { "name": "version", "type": "uint16" }, - { "name": "schema", "type": "bytes32" }, - { "name": "recipient", "type": "address" }, - { "name": "time", "type": "uint64" }, - { "name": "expirationTime", "type": "uint64" }, - { "name": "revocable", "type": "bool" }, - { "name": "refUID", "type": "bytes32" }, - { "name": "data", "type": "bytes" } - ] - }, - "signature": { - "v": 27, - "r": "0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7", - "s": "0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e" - } - } - - -test('Encode eip712 message', async () => { - const node = fromEip712(testData) - - expect(node._sig.length).toEqual(268) - expect(node.attachment instanceof Uint8Array).toBeTruthy() - console.log(node) -}) - - -test('Canonicalize ipld eip712 object', async () => { - const node = fromEip712(testData) - const tape = new BytesTape(node._sig) - tape.readVarint() // skip sigil - tape.readVarint() // skip key type - tape.read(1) // skip recovery bit - tape.readVarint() // skip hash type - tape.readVarint() // skip canonicalizer codec - const can1 = setupCanonicalizer(tape.remainder) - expect(can1.remainder.length).toEqual(64) - delete node._sig - delete testData.signature - const res1 = can1.canonicalize(node) - expect(res1.decoded).toEqual(testData) - expect(res1.digest).toEqual(expectedHash) -}) - -test.only('Generate test vectors', async () => { - const signature = await walletClient.signTypedData(testData) - console.log('sig', signature) - - function putEntry (car, eip712, node, error) { - const entry = { - valid: error ? false : true, - data: eip712 ? car.put(eip712) : null, - node: node ? car.put(node) : null - } - if (error) entry.error = error - return car.put(entry) - } - - const car = (new CARFactory()).build() - const entries = [] - entries.push(putEntry(car, testData, fromEip712(testData))) - entries.push(putEntry(car, easData, fromEip712(easData))) - // invalid stuff - const invalidData1 = { ...testData, signature: "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b" } - entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) - - const invalidNode1 = fromEip712(testData) - invalidNode1._sig.set([0xec], 1) - entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) - const invalidNode2 = fromEip712(testData) - invalidNode2._sig.set([0x00], 2) - entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) - const invalidNode3 = fromEip712(testData) - invalidNode3._sig.set([0x12], 3) - entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) - - car.put({ - canonicalization: 'eip712', - signature: 'secp256k1', - hash: 'keccak256', - entries - }, { isRoot: true }) - - await pipeline(car, fs.createWriteStream("./eip712-secp256k1.car")); -}) From ab58598d53d6a4e364d8e052be16b9a5e1b915d2 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 18:52:39 +0300 Subject: [PATCH 062/110] wip --- packages/varsig/package.json | 3 +- packages/varsig/src/__tests__/eip712.test.ts | 36 ++++++++++++++++++++ packages/varsig/src/encoding/eip712.ts | 3 ++ packages/varsig/src/signing.ts | 5 +-- pnpm-lock.yaml | 11 +++--- 5 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 packages/varsig/src/__tests__/eip712.test.ts diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 86c64768..45b1e594 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -45,7 +45,8 @@ }, "homepage": "https://github.com/ceramicnetwork/js-did#readme", "devDependencies": { - "@stablelib/random": "^1.0.2" + "@stablelib/random": "^1.0.2", + "cartonne": "^2.2.0" }, "dependencies": { "@noble/curves": "^1.2.0", diff --git a/packages/varsig/src/__tests__/eip712.test.ts b/packages/varsig/src/__tests__/eip712.test.ts new file mode 100644 index 00000000..0cfb3844 --- /dev/null +++ b/packages/varsig/src/__tests__/eip712.test.ts @@ -0,0 +1,36 @@ +import { readFile } from 'node:fs/promises' +import { test } from '@jest/globals' +import { CARFactory } from 'cartonne' +import { Decoder } from '../decoder' +import { BytesTape } from '../bytes-tape' +import {CanonicalizationKind} from "../canonicalization"; +import {decompressDomain, decompressTypes} from "../encoding/eip712"; +import {secp256k1} from "@noble/curves/secp256k1"; + +const factory = new CARFactory() + +test('eip712-secp256k1.car', async () => { + const carFilepath = new URL('../../test/__vectors__/eip712-secp256k1.car', import.meta.url) + const carBytes = await readFile(carFilepath) + const car = factory.fromBytes(carBytes) + const root = car.get(car.roots[0]) + if (!root) throw new Error(`Empty root`) + for (const entryCID of root.entries) { + const a = car.get(entryCID) + if (a.valid) { + const dataCID = a.data + const nodeCID = a.node + const data = car.get(dataCID) + const node = car.get(nodeCID) + const varsig = new Decoder(new BytesTape(node._sig)).read() + if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) + const input = varsig.canonicalization(data.message) + let signature = secp256k1.Signature.fromCompact(varsig.signature) + if (varsig.signing.recoveryBit) { + signature = signature.addRecoveryBit(varsig.signing.recoveryBit - 27) + } + console.log('pub.1', signature.recoverPublicKey(input).toHex(false)) + console.log('pub.0', data, node) + } + } +}) diff --git a/packages/varsig/src/encoding/eip712.ts b/packages/varsig/src/encoding/eip712.ts index 1bdd382d..86d1c3f2 100644 --- a/packages/varsig/src/encoding/eip712.ts +++ b/packages/varsig/src/encoding/eip712.ts @@ -228,8 +228,11 @@ function compressTypes(types: Eip712Types): CompressedTypes { const FULL_TYPES = { u: 'uint', + u16: 'uint16', + u64: 'uint64', i: 'int', b: 'bytes', + b32: 'bytes32', s: 'string', a: 'address', o: 'bool', diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 65d2e95a..6603fae9 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -12,7 +12,7 @@ export enum SigningKind { export type SigningSecp256k1 = { kind: SigningKind.SECP256K1 - verify: ( + verify( signature: Uint8Array, verificationKey: VerificationKey, digest: Uint8Array @@ -38,7 +38,8 @@ export class SigningDecoder { } return { kind: SigningKind.SECP256K1, - verify: async () => Promise.resolve(false) + recoveryBit: recoveryBit || undefined, + verify: async () => Promise.resolve(false), } } case SigningKind.RSA: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a2cf564..bc4035f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -582,9 +582,6 @@ importers: '@types/node': specifier: ^20.2.3 version: 20.2.3 - cartonne: - specifier: ^2.2.0 - version: 2.2.0 codeco: specifier: ^1.1.0 version: 1.1.0 @@ -610,6 +607,9 @@ importers: '@stablelib/random': specifier: ^1.0.2 version: 1.0.2 + cartonne: + specifier: ^2.2.0 + version: 2.2.0 website: dependencies: @@ -8771,7 +8771,6 @@ packages: multiformats: 11.0.2 multihashes-sync: 1.1.1 varintes: 2.0.5 - dev: false /catering@2.1.1: resolution: {integrity: sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==} @@ -14863,7 +14862,6 @@ packages: dependencies: '@noble/hashes': 1.3.2 multiformats: 11.0.2 - dev: false /murmurhash3js-revisited@3.0.0: resolution: {integrity: sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==} @@ -18874,7 +18872,6 @@ packages: /varintes@2.0.5: resolution: {integrity: sha512-iF3jlHLko9NrYjaUZvT3VwypP3V20KNNhT1tzqblyIyrVjNiW7HseGOhuP+apgZBp9X/8+5pxa7kNikhJeZlIw==} - dev: false /varuint-bitcoin@1.1.2: resolution: {integrity: sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==} From 4682c2692f7e4e51e5c7d9d6edbfff18ccf82069 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 18:55:34 +0300 Subject: [PATCH 063/110] wip --- packages/varsig/src/canonicalization.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index c156e034..88e96be9 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -1,10 +1,10 @@ import { BytesTape } from './bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { UnreacheableCaseError } from './unreachable-case-error.js' -import { hashTypedData } from 'viem' import { Eip712 } from './canons/eip712.js' import { HashingAlgo } from './hashing.js' import { keccak_256 } from '@noble/hashes/sha3' +import type { SigningKind } from './signing.js' export enum CanonicalizationKind { EIP712 = Eip712.SIGIL, From 512c91bb8b5b322f52c91d28c3635e9820364c03 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 19:43:17 +0300 Subject: [PATCH 064/110] Fix secp tests --- packages/varsig/src/signing.ts | 29 ++++++++++++------- packages/varsig/test/parse-eip191.test.ts | 35 +++++++++++++++++++---- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index e06d06d8..095208be 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -1,5 +1,8 @@ import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' +import { secp256k1 } from '@noble/curves/secp256k1' +import * as uint8arrays from 'uint8arrays' +import { keccak_256 } from '@noble/hashes/sha3' type EthAddress = `0x${string}` type PublicKey = Uint8Array @@ -41,19 +44,23 @@ export class SigningDecoder { return { kind: SigningKind.SECP256K1, verify: async (input, signature, verificationKey) => { - let k1Sig = secp256k1.Signature.fromCompact(decoder.signature) + let k1Sig = secp256k1.Signature.fromCompact(signature) if (recoveryBit) { k1Sig = k1Sig.addRecoveryBit(recoveryBit - 27) - } - const recoveredKey = k1Sig.recoverPublicKey(input) - // compare recoveredKey with verificationKey - if (verificationKey instanceof Uint8Array) { - return recoveredKey.toBytes().equals(verificationKey) - } - else if (typeof verificationKey === 'string') { - // convert recoveredKey to eth address - const recoveredAddress = '0x' + recoveredKey.toBytes().slice(-20).toString('hex') - return recoveredAddress === verificationKey + const recoveredKey = k1Sig.recoverPublicKey(input).toRawBytes(false) + // compare recoveredKey with verificationKey + if (verificationKey instanceof Uint8Array) { + return uint8arrays.equals(recoveredKey, verificationKey) + } + // convert recoveredKey to eth address and compare + else if (typeof verificationKey === 'string') { + const recoveredAddress = `0x${uint8arrays.toString( + keccak_256(recoveredKey.slice(1)) , 'base16' + ).slice(-40)}` + return recoveredAddress === verificationKey.toLowerCase() + } + } else { + return secp256k1.verify(signature, input, verificationKey) } } } diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/test/parse-eip191.test.ts index 59112ac5..d3f5c16c 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/test/parse-eip191.test.ts @@ -12,7 +12,7 @@ test('validate eip191', async () => { const account = privateKeyToAccount( '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' ) - console.log('pub', account.publicKey) + const verificationKey = uint8arrays.fromString(account.publicKey.slice(2), 'base16') const stringSignature = await account.signMessage({ message: 'Hello World' }) const signatureBytes = uint8arrays.fromString( stringSignature.toLowerCase().replace(/^0x/, ''), @@ -28,10 +28,33 @@ test('validate eip191', async () => { ]) // const a = decode(varsig) const decoder = new Decoder(new BytesTape(varsig)).read() + + const input = decoder.canonicalization('Hello World') + expect(await decoder.signing.verify(input, decoder.signature, verificationKey)).toBeTruthy() + expect(await decoder.signing.verify(input, decoder.signature, account.address)).toBeTruthy() +}) + +test('validate eip191, no recovery bit', async () => { + const account = privateKeyToAccount( + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + ) + const verificationKey = uint8arrays.fromString(account.publicKey.slice(2), 'base16') + const stringSignature = await account.signMessage({ message: 'Hello World' }) + const signatureBytes = uint8arrays.fromString( + stringSignature.toLowerCase().replace(/^0x/, ''), + 'hex' + ) + const varsig = uint8arrays.concat([ + hex(0x34), + varintes.encode(0xe7)[0], + [0x00], + varintes.encode(0x1b)[0], + varintes.encode(CanonicalizationKind.EIP191)[0], + signatureBytes.subarray(0, 64), + ]) + // const a = decode(varsig) + const decoder = new Decoder(new BytesTape(varsig)).read() + const input = decoder.canonicalization('Hello World') - let signature = secp256k1.Signature.fromCompact(decoder.signature) - if (decoder.signing.recoveryBit) { - signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) - } - console.log(signature.recoverPublicKey(input).toHex(false)) + expect(decoder.signing.verify(input, decoder.signature, verificationKey)).toBeTruthy() }) From 83050317c5ac5c760636236c2202564cabee090f Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 19:53:26 +0300 Subject: [PATCH 065/110] Fix more tests --- packages/varsig/test/canons/eip712.test.ts | 319 ++++++++++----------- packages/varsig/test/parse-eip712.test.ts | 9 +- 2 files changed, 160 insertions(+), 168 deletions(-) diff --git a/packages/varsig/test/canons/eip712.test.ts b/packages/varsig/test/canons/eip712.test.ts index 382fbbe0..21d94107 100644 --- a/packages/varsig/test/canons/eip712.test.ts +++ b/packages/varsig/test/canons/eip712.test.ts @@ -1,161 +1,158 @@ -// import { fromEip712, setupCanonicalizer } from '../../src/canons/eip712.ts' -// import { BytesTape } from '../../src/bytes-tape.ts' -// import * as uint8arrays from 'uint8arrays' -// import { createWalletClient, custom } from 'viem' -// import { privateKeyToAccount } from 'viem/accounts' -// import * as fs from "node:fs" -// import { pipeline } from "node:stream/promises" -// import { CARFactory, CarBlock } from "cartonne" -// -// const walletClient = createWalletClient({ -// account: privateKeyToAccount('0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504'), -// transport: custom({ request: async () => {}}) -// }) -// -// const testData = { -// "types": { -// "EIP712Domain": [ -// { "name": "name", "type": "string" }, -// { "name": "version", "type": "string" }, -// { "name": "chainId", "type": "uint256" }, -// { "name": "verifyingContract", "type": "address" } -// ], -// "Person": [ -// { "name": "name", "type": "string" }, -// { "name": "wallet", "type": "address" } -// ], -// "Mail": [ -// { "name": "from", "type": "Person" }, -// { "name": "to", "type": "Person" }, -// { "name": "contents", "type": "string" }, -// { "name": "attachment", "type": "bytes" } -// ] -// }, -// "primaryType": "Mail", -// "domain": { -// "name": "Ether Mail", -// "version": "1", -// "chainId": 1, -// "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" -// }, -// "message": { -// "from": { -// "name": "Cow", -// "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" -// }, -// "to": { -// "name": "Bob", -// "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" -// }, -// "contents": "Hello, Bob!", -// "attachment": "0xababababababababababa83459873459873459873498575986734359" -// }, -// "signature": "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b" -// } -// const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') -// -// const easData = { -// "domain": { -// "name": "EAS Attestation", -// "version": "0.26", -// "chainId": 1, -// "verifyingContract": "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587" -// }, -// "primaryType": "Attest", -// "message": { -// "schema": "0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7", -// "recipient": "0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2", -// "refUID": "0x0000000000000000000000000000000000000000000000000000000000000000", -// "data": "0x0000000000000000000000000000000000000000000000000000000000000001", -// "time": 1699288761, -// "revocable": true, -// "expirationTime": 0, -// "version": 1, -// }, -// "types": { -// "Attest": [ -// { "name": "version", "type": "uint16" }, -// { "name": "schema", "type": "bytes32" }, -// { "name": "recipient", "type": "address" }, -// { "name": "time", "type": "uint64" }, -// { "name": "expirationTime", "type": "uint64" }, -// { "name": "revocable", "type": "bool" }, -// { "name": "refUID", "type": "bytes32" }, -// { "name": "data", "type": "bytes" } -// ] -// }, -// "signature": { -// "v": 27, -// "r": "0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7", -// "s": "0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e" -// } -// } -// -// -// test('Encode eip712 message', async () => { -// const node = fromEip712(testData) -// -// expect(node._sig.length).toEqual(268) -// expect(node.attachment instanceof Uint8Array).toBeTruthy() -// console.log(node) -// }) -// -// -// test('Canonicalize ipld eip712 object', async () => { -// const node = fromEip712(testData) -// const tape = new BytesTape(node._sig) -// tape.readVarint() // skip sigil -// tape.readVarint() // skip key type -// tape.read(1) // skip recovery bit -// tape.readVarint() // skip hash type -// tape.readVarint() // skip canonicalizer codec -// const can1 = setupCanonicalizer(tape.remainder) -// expect(can1.remainder.length).toEqual(64) -// delete node._sig -// delete testData.signature -// const res1 = can1.canonicalize(node) -// expect(res1.decoded).toEqual(testData) -// expect(res1.digest).toEqual(expectedHash) -// }) -// -// test.only('Generate test vectors', async () => { -// const signature = await walletClient.signTypedData(testData) -// console.log('sig', signature) -// -// function putEntry (car, eip712, node, error) { -// const entry = { -// valid: error ? false : true, -// data: eip712 ? car.put(eip712) : null, -// node: node ? car.put(node) : null -// } -// if (error) entry.error = error -// return car.put(entry) -// } -// -// const car = (new CARFactory()).build() -// const entries = [] -// entries.push(putEntry(car, testData, fromEip712(testData))) -// entries.push(putEntry(car, easData, fromEip712(easData))) -// // invalid stuff -// const invalidData1 = { ...testData, signature: "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b" } -// entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) -// -// const invalidNode1 = fromEip712(testData) -// invalidNode1._sig.set([0xec], 1) -// entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) -// const invalidNode2 = fromEip712(testData) -// invalidNode2._sig.set([0x00], 2) -// entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) -// const invalidNode3 = fromEip712(testData) -// invalidNode3._sig.set([0x12], 3) -// entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) -// -// car.put({ -// canonicalization: 'eip712', -// signature: 'secp256k1', -// hash: 'keccak256', -// entries -// }, { isRoot: true }) -// -// await pipeline(car, fs.createWriteStream("./eip712-secp256k1.car")); -// }) +import { fromEip712, prepareCanonicalization } from '../../src/canons/eip712.ts' +import { BytesTape } from '../../src/bytes-tape.ts' +import * as uint8arrays from 'uint8arrays' +import { createWalletClient, custom } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import * as fs from "node:fs" +import { pipeline } from "node:stream/promises" +import { CARFactory, CarBlock } from "cartonne" + +const walletClient = createWalletClient({ + account: privateKeyToAccount('0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504'), + transport: custom({ request: async () => {}}) +}) + +const testData = { + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallet", "type": "address" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" }, + { "name": "attachment", "type": "bytes" } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!", + "attachment": "0xababababababababababa83459873459873459873498575986734359" + }, + "signature": "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b" +} +const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') + +const easData = { + "domain": { + "name": "EAS Attestation", + "version": "0.26", + "chainId": 1, + "verifyingContract": "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587" + }, + "primaryType": "Attest", + "message": { + "schema": "0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7", + "recipient": "0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2", + "refUID": "0x0000000000000000000000000000000000000000000000000000000000000000", + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "time": 1699288761, + "revocable": true, + "expirationTime": 0, + "version": 1, + }, + "types": { + "Attest": [ + { "name": "version", "type": "uint16" }, + { "name": "schema", "type": "bytes32" }, + { "name": "recipient", "type": "address" }, + { "name": "time", "type": "uint64" }, + { "name": "expirationTime", "type": "uint64" }, + { "name": "revocable", "type": "bool" }, + { "name": "refUID", "type": "bytes32" }, + { "name": "data", "type": "bytes" } + ] + }, + "signature": { + "v": 27, + "r": "0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7", + "s": "0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e" + } + } + + +test('Encode eip712 message', async () => { + const node = fromEip712(testData) + + expect(node._sig.length).toEqual(268) + expect(node.attachment instanceof Uint8Array).toBeTruthy() +}) + + +test('Canonicalize ipld eip712 object', async () => { + const node = fromEip712(testData) + const tape = new BytesTape(node._sig) + tape.readVarint() // skip sigil + tape.readVarint() // skip key type + tape.read(1) // skip recovery bit + tape.readVarint() // skip hash type + tape.readVarint() // skip canonicalizer codec + const can = prepareCanonicalization(tape, 0x1b, 0xe7) + expect(tape.remainder.length).toEqual(64) + delete node._sig + const sigInput = can(node) + expect(sigInput).toEqual(expectedHash) +}) + +test.skip('Generate test vectors', async () => { + const signature = await walletClient.signTypedData(testData) + console.log('sig', signature) + + function putEntry (car, eip712, node, error) { + const entry = { + valid: error ? false : true, + data: eip712 ? car.put(eip712) : null, + node: node ? car.put(node) : null + } + if (error) entry.error = error + return car.put(entry) + } + + const car = (new CARFactory()).build() + const entries = [] + entries.push(putEntry(car, testData, fromEip712(testData))) + entries.push(putEntry(car, easData, fromEip712(easData))) + // invalid stuff + const invalidData1 = { ...testData, signature: "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b" } + entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) + + const invalidNode1 = fromEip712(testData) + invalidNode1._sig.set([0xec], 1) + entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) + const invalidNode2 = fromEip712(testData) + invalidNode2._sig.set([0x00], 2) + entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) + const invalidNode3 = fromEip712(testData) + invalidNode3._sig.set([0x12], 3) + entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) + + car.put({ + canonicalization: 'eip712', + signature: 'secp256k1', + hash: 'keccak256', + entries + }, { isRoot: true }) + + await pipeline(car, fs.createWriteStream("./eip712-secp256k1.car")); +}) diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index 273f1fd9..4bcafced 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -5,7 +5,7 @@ import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../src/bytes-tape.js' import { CanonicalizationKind } from '../src/canonicalization.js' -import { fromEip712A } from '../src/encoding/eip712' +import { fromEip712A } from '../src/canons/eip712' import { Decoder } from '../src/decoder.js' import { hex } from '../src/__tests__/hex.util.js' @@ -83,7 +83,6 @@ test('712 flow', async () => { const account = privateKeyToAccount( '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' ) - console.log('pub.0', account.publicKey) const stringSignature = await account.signTypedData({ domain: { ...testData.domain, chainId: 1n }, types: testData.types, @@ -113,9 +112,5 @@ test('712 flow', async () => { const decoder = new Decoder(new BytesTape(varsig)).read() if (decoder.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) const input = decoder.canonicalization(testData.message) - let signature = secp256k1.Signature.fromCompact(decoder.signature) - if (decoder.signing.recoveryBit) { - signature = signature.addRecoveryBit(decoder.signing.recoveryBit - 27) - } - console.log('pub.1', signature.recoverPublicKey(input).toHex(false)) + expect(await decoder.signing.verify(input, decoder.signature, account.address)).toBeTruthy() }) From 755f741e71ae5316a9f8d7aeb0a2b2e2b7793dfb Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 20:12:45 +0300 Subject: [PATCH 066/110] Clean up singning --- packages/varsig/src/canonicalization.ts | 19 ---------- packages/varsig/src/signing.ts | 41 +++----------------- packages/varsig/src/signing/secp256k1.ts | 48 ++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 54 deletions(-) create mode 100644 packages/varsig/src/signing/secp256k1.ts diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 88e96be9..90a5a052 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -36,25 +36,6 @@ export class CanonicalizationDecoder { switch (sigil) { case Eip712.SIGIL: return Eip712.prepareCanonicalization(this.tape, hashing, sigKind) - // case CanonicalizationKind.EIP712: { - // if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP712 mandates use of KECCAK 256`) - // const metadataLength = this.tape.readVarint() - // const metadataBytes = this.tape.read(metadataLength) - // const metadata = JSON.parse(uint8arrays.toString(metadataBytes)) - // const [types, primaryType, domain] = metadata - // const fn = (message: any) => { - // const decoded = { - // domain: decompressDomain(domain as CompressedDomain), - // message: message, - // primaryType: primaryType, - // types: decompressTypes(types), - // } - // const digestHex = hashTypedData(decoded) - // return uint8arrays.fromString(digestHex.toLowerCase().replace(/^0x/, ''), 'hex') - // } - // fn.kind = CanonicalizationKind.EIP712 - // return fn - // } case CanonicalizationKind.EIP191: { if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) const fn = (message: string) => { diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 095208be..ede264d7 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -1,8 +1,6 @@ import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' -import { secp256k1 } from '@noble/curves/secp256k1' -import * as uint8arrays from 'uint8arrays' -import { keccak_256 } from '@noble/hashes/sha3' +import { Secp256k1 } from './signing/secp256k1.js' type EthAddress = `0x${string}` type PublicKey = Uint8Array @@ -10,11 +8,11 @@ type VerificationKey = PublicKey | EthAddress export enum SigningKind { RSA = 0x1205, - SECP256K1 = 0xe7, + SECP256K1 = Secp256k1.SIGIL, } -export type SigningSecp256k1 = { - kind: SigningKind.SECP256K1 +export type SigningAlgo = { + kind: SigningKind verify: VerifySignatureFn } @@ -24,7 +22,6 @@ export type VerifySignatureFn = ( verificationKey: VerificationKey ) => Promise -export type SigningAlgo = SigningSecp256k1 export class SigningDecoder { constructor(private readonly tape: BytesTape) {} @@ -37,33 +34,7 @@ export class SigningDecoder { const signingSigil = this.tape.readVarint() switch (signingSigil) { case SigningKind.SECP256K1: { - const recoveryBit = this.tape.read(1)[0] - if (recoveryBit && !(recoveryBit === 27 || recoveryBit === 28)) { - throw new Error(`Wrong recovery bit`) - } - return { - kind: SigningKind.SECP256K1, - verify: async (input, signature, verificationKey) => { - let k1Sig = secp256k1.Signature.fromCompact(signature) - if (recoveryBit) { - k1Sig = k1Sig.addRecoveryBit(recoveryBit - 27) - const recoveredKey = k1Sig.recoverPublicKey(input).toRawBytes(false) - // compare recoveredKey with verificationKey - if (verificationKey instanceof Uint8Array) { - return uint8arrays.equals(recoveredKey, verificationKey) - } - // convert recoveredKey to eth address and compare - else if (typeof verificationKey === 'string') { - const recoveredAddress = `0x${uint8arrays.toString( - keccak_256(recoveredKey.slice(1)) , 'base16' - ).slice(-40)}` - return recoveredAddress === verificationKey.toLowerCase() - } - } else { - return secp256k1.verify(signature, input, verificationKey) - } - } - } + return Secp256k1.prepareVerifier(this.tape) } case SigningKind.RSA: throw new Error(`Not implemented: signingSigil: RSA`) @@ -75,7 +46,7 @@ export class SigningDecoder { readSignature(signing: SigningAlgo): Uint8Array { switch (signing.kind) { case SigningKind.SECP256K1: { - return this.tape.read(64) + return Secp256k1.readSignature(this.tape) } default: throw new UnreacheableCaseError(signing.kind, 'signing kind') diff --git a/packages/varsig/src/signing/secp256k1.ts b/packages/varsig/src/signing/secp256k1.ts new file mode 100644 index 00000000..868c98c5 --- /dev/null +++ b/packages/varsig/src/signing/secp256k1.ts @@ -0,0 +1,48 @@ +import type { BytesTape } from './bytes-tape.js' +import { SigningAlgo } from '../signing.js' + +import { secp256k1 } from '@noble/curves/secp256k1' +import * as uint8arrays from 'uint8arrays' +import { keccak_256 } from '@noble/hashes/sha3' + + + + +const SIGIL = 0xe7 + +function prepareVerifier(tape: ByteTape): SigningAlgo { + const recoveryBit = tape.read(1)[0] + if (recoveryBit && !(recoveryBit === 27 || recoveryBit === 28)) { + throw new Error(`Wrong recovery bit`) + } + return { + kind: SIGIL, + verify: async (input, signature, verificationKey) => { + let k1Sig = secp256k1.Signature.fromCompact(signature) + if (recoveryBit) { + k1Sig = k1Sig.addRecoveryBit(recoveryBit - 27) + const recoveredKey = k1Sig.recoverPublicKey(input).toRawBytes(false) + // compare recoveredKey with verificationKey + if (verificationKey instanceof Uint8Array) { + return uint8arrays.equals(recoveredKey, verificationKey) + } + // convert recoveredKey to eth address and compare + else if (typeof verificationKey === 'string') { + const recoveredAddress = `0x${uint8arrays.toString( + keccak_256(recoveredKey.slice(1)) , 'base16' + ).slice(-40)}` + return recoveredAddress === verificationKey.toLowerCase() + } + } else { + return secp256k1.verify(signature, input, verificationKey) + } + } + } +} + +function readSignature(tape: BytesTape): Uint8Array { + return tape.read(64) +} + + +export const Secp256k1 = { SIGIL, prepareVerifier, readSignature } From af1a4a27fc564ec43f448fbe21903b7572af4a5f Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 23 Nov 2023 21:42:37 +0300 Subject: [PATCH 067/110] Add way to recover original --- .../src/__tests__/canonicalization.test.ts | 2 +- packages/varsig/src/__tests__/eip712.test.ts | 2 +- packages/varsig/src/__tests__/signing.test.ts | 1 - packages/varsig/src/canons/eip712.ts | 16 +++++++++++++--- packages/varsig/src/signing/secp256k1.ts | 1 + packages/varsig/src/varsig.ts | 12 ++++++------ packages/varsig/test/canons/eip712.test.ts | 2 +- packages/varsig/test/parse-eip712.test.ts | 2 +- 8 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index 51aeeb05..124bfde2 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -90,7 +90,7 @@ test('EIP712', () => { const canonicalization = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256, SigningKind.SECP256K1) expect(canonicalization.kind).toEqual(CanonicalizationKind.EIP712) if (canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error() - const input = toString(canonicalization(TEST_DATA.message), 'hex') + const input = toString(canonicalization.canonicalization(TEST_DATA.message), 'hex') expect(input).toEqual('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4') }) diff --git a/packages/varsig/src/__tests__/eip712.test.ts b/packages/varsig/src/__tests__/eip712.test.ts index 0cfb3844..9c511240 100644 --- a/packages/varsig/src/__tests__/eip712.test.ts +++ b/packages/varsig/src/__tests__/eip712.test.ts @@ -24,7 +24,7 @@ test('eip712-secp256k1.car', async () => { const node = car.get(nodeCID) const varsig = new Decoder(new BytesTape(node._sig)).read() if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - const input = varsig.canonicalization(data.message) + const input = varsig.canonicalization.canonicalization(data.message) let signature = secp256k1.Signature.fromCompact(varsig.signature) if (varsig.signing.recoveryBit) { signature = signature.addRecoveryBit(varsig.signing.recoveryBit - 27) diff --git a/packages/varsig/src/__tests__/signing.test.ts b/packages/varsig/src/__tests__/signing.test.ts index 3e749946..58ad5f9d 100644 --- a/packages/varsig/src/__tests__/signing.test.ts +++ b/packages/varsig/src/__tests__/signing.test.ts @@ -47,7 +47,6 @@ describe('secp265k1', () => { const decoder = new SigningDecoder(tape) const result = decoder.read() expect(result.kind).toEqual(SigningKind.SECP256K1) - // expect(result.recoveryBit).toEqual(27) }) test('readSignature', () => { const decoder = new SigningDecoder(tape) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index e791efa5..3b731f67 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -72,14 +72,24 @@ export function prepareCanonicalization( primaryType, domain: decompressDomain(domain), } - const fn = (node: IpldNode) => { + const can = (node: IpldNode) => { const message = ipldNodeToMessage(node) // @ts-ignore const hexHash = hashTypedData({ ...metadata, message }) return uint8arrays.fromString(hexHash.slice(2), 'base16') } - fn.kind = SIGIL - return fn + const original = (node: IpldNode, signature: Uint8Array, recoveryBit: number) => { + const message = ipldNodeToMessage(node) + + const sigBytes = uint8arrays.concat([signature, [recoveryBit]]) + const sigHex = `0x${uint8arrays.toString(sigBytes, 'base16')}` + return { ...metadata, message, signature: signHex } + } + return { + kind: SIGIL, + canonicalization: can, + original, + } } export const Eip712 = { SIGIL, prepareCanonicalization } diff --git a/packages/varsig/src/signing/secp256k1.ts b/packages/varsig/src/signing/secp256k1.ts index 868c98c5..074770c3 100644 --- a/packages/varsig/src/signing/secp256k1.ts +++ b/packages/varsig/src/signing/secp256k1.ts @@ -17,6 +17,7 @@ function prepareVerifier(tape: ByteTape): SigningAlgo { } return { kind: SIGIL, + recoveryBit: recoveryBit || undefined, verify: async (input, signature, verificationKey) => { let k1Sig = secp256k1.Signature.fromCompact(signature) if (recoveryBit) { diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index c7986303..2dc9c616 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -16,14 +16,14 @@ export async function verify ( const { canonicalization, signing, signature } = (new Decoder(node._sig)).read() delete node._sig - const signatureInput = canonicalization(node) + const signatureInput = canonicalization.canonicalization(node) return signing.verify(signatureInput, signature, verificationKey) } } -// export async function toOriginal (node: VarsigNode): Promise { -// const { canonicalization } = (new Decoder(node._sig)).read() -// delete node._sig -// // return canonicalization(node) -// } +export async function toOriginal (node: VarsigNode): Promise { + const { canonicalization, signing, signature } = (new Decoder(node._sig)).read() + delete node._sig + return canonicalization.original(node, signature, signing.recoveryBig) +} diff --git a/packages/varsig/test/canons/eip712.test.ts b/packages/varsig/test/canons/eip712.test.ts index 21d94107..ddaac272 100644 --- a/packages/varsig/test/canons/eip712.test.ts +++ b/packages/varsig/test/canons/eip712.test.ts @@ -111,7 +111,7 @@ test('Canonicalize ipld eip712 object', async () => { const can = prepareCanonicalization(tape, 0x1b, 0xe7) expect(tape.remainder.length).toEqual(64) delete node._sig - const sigInput = can(node) + const sigInput = can.canonicalization(node) expect(sigInput).toEqual(expectedHash) }) diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index 4bcafced..47456921 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -111,6 +111,6 @@ test('712 flow', async () => { ]) const decoder = new Decoder(new BytesTape(varsig)).read() if (decoder.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - const input = decoder.canonicalization(testData.message) + const input = decoder.canonicalization.canonicalization(testData.message) expect(await decoder.signing.verify(input, decoder.signature, account.address)).toBeTruthy() }) From 0105edb019bf8c4272bf1f026037953b84d921a6 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 21:46:49 +0300 Subject: [PATCH 068/110] types --- packages/varsig/src/canonicalization.ts | 5 ++--- packages/varsig/src/canons/eip712.ts | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 90a5a052..529fbf41 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -16,10 +16,9 @@ type CanonicalizationEIP191 = { (message: string): Uint8Array } -type SignatureInput = Uint8Array type CanonicalizationEIP712 = { kind: CanonicalizationKind.EIP712 - (message: any): SignatureInput + (message: any): Uint8Array } export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP712 @@ -34,7 +33,7 @@ export class CanonicalizationDecoder { read(hashing: HashingAlgo, sigKind: SigningKind): CanonicalizationAlgo { const sigil = this.tape.readVarint() switch (sigil) { - case Eip712.SIGIL: + case CanonicalizationKind.EIP712: return Eip712.prepareCanonicalization(this.tape, hashing, sigKind) case CanonicalizationKind.EIP191: { if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 3b731f67..4e7a0077 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -3,6 +3,7 @@ import type { HashingAlgo } from '../hashing.js' import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' import { hashTypedData, Hex, TypedDataDomain } from 'viem' +import type { CanonicalizationAlgo } from '../canonicalization.js' interface Eip712Domain { name: string @@ -61,7 +62,7 @@ export function prepareCanonicalization( tape: BytesTape, hashType: HashingAlgo, keyType: SignatureKind -): Canonicalization { +): CanonicalizationAlgo { if (hashType !== SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashType}`) if (!SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) const metadataLength = tape.readVarint() From 186daca0346545dd1f5c14a9bbc2bef8689e85aa Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 21:48:16 +0300 Subject: [PATCH 069/110] types --- .../src/__tests__/canonicalization.test.ts | 6 +- packages/varsig/src/canonicalization.ts | 2 +- packages/varsig/test/canons/eip712.test.ts | 185 ++++++++++-------- 3 files changed, 104 insertions(+), 89 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index 124bfde2..ebafcade 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -87,7 +87,11 @@ test('EIP712', () => { }) const bytes = concat([encode(0xe712)[0], a.params]) const tape = new BytesTape(bytes) - const canonicalization = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256, SigningKind.SECP256K1) + const canonicalization = CanonicalizationDecoder.read( + tape, + HashingAlgo.KECCAK256, + SigningKind.SECP256K1 + ) expect(canonicalization.kind).toEqual(CanonicalizationKind.EIP712) if (canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error() const input = toString(canonicalization.canonicalization(TEST_DATA.message), 'hex') diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 529fbf41..888e5f6d 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -26,7 +26,7 @@ export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP7 export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} - static read(tape: BytesTape, hashing: HashingAlgo, sigKind: SigningKind) { + static read(tape: BytesTape, hashing: HashingAlgo, sigKind: SigningKind): CanonicalizationAlgo { return new CanonicalizationDecoder(tape).read(hashing, sigKind) } diff --git a/packages/varsig/test/canons/eip712.test.ts b/packages/varsig/test/canons/eip712.test.ts index ddaac272..1c7952a0 100644 --- a/packages/varsig/test/canons/eip712.test.ts +++ b/packages/varsig/test/canons/eip712.test.ts @@ -1,96 +1,101 @@ -import { fromEip712, prepareCanonicalization } from '../../src/canons/eip712.ts' -import { BytesTape } from '../../src/bytes-tape.ts' +import { fromEip712, prepareCanonicalization } from '../../src/canons/eip712.js' +import { BytesTape } from '../../src/bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import * as fs from "node:fs" -import { pipeline } from "node:stream/promises" -import { CARFactory, CarBlock } from "cartonne" +import * as fs from 'node:fs' +import { pipeline } from 'node:stream/promises' +import { CARFactory, CarBlock } from 'cartonne' const walletClient = createWalletClient({ - account: privateKeyToAccount('0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504'), - transport: custom({ request: async () => {}}) + account: privateKeyToAccount( + '0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504' + ), + transport: custom({ request: async () => {} }), }) const testData = { - "types": { - "EIP712Domain": [ - { "name": "name", "type": "string" }, - { "name": "version", "type": "string" }, - { "name": "chainId", "type": "uint256" }, - { "name": "verifyingContract", "type": "address" } + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, ], - "Person": [ - { "name": "name", "type": "string" }, - { "name": "wallet", "type": "address" } + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + { name: 'attachment', type: 'bytes' }, ], - "Mail": [ - { "name": "from", "type": "Person" }, - { "name": "to", "type": "Person" }, - { "name": "contents", "type": "string" }, - { "name": "attachment", "type": "bytes" } - ] }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', }, - "contents": "Hello, Bob!", - "attachment": "0xababababababababababa83459873459873459873498575986734359" + contents: 'Hello, Bob!', + attachment: '0xababababababababababa83459873459873459873498575986734359', }, - "signature": "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b" + signature: + '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b', } -const expectedHash = uint8arrays.fromString('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16') +const expectedHash = uint8arrays.fromString( + '703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', + 'base16' +) const easData = { - "domain": { - "name": "EAS Attestation", - "version": "0.26", - "chainId": 1, - "verifyingContract": "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587" - }, - "primaryType": "Attest", - "message": { - "schema": "0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7", - "recipient": "0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2", - "refUID": "0x0000000000000000000000000000000000000000000000000000000000000000", - "data": "0x0000000000000000000000000000000000000000000000000000000000000001", - "time": 1699288761, - "revocable": true, - "expirationTime": 0, - "version": 1, - }, - "types": { - "Attest": [ - { "name": "version", "type": "uint16" }, - { "name": "schema", "type": "bytes32" }, - { "name": "recipient", "type": "address" }, - { "name": "time", "type": "uint64" }, - { "name": "expirationTime", "type": "uint64" }, - { "name": "revocable", "type": "bool" }, - { "name": "refUID", "type": "bytes32" }, - { "name": "data", "type": "bytes" } - ] - }, - "signature": { - "v": 27, - "r": "0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7", - "s": "0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e" - } - } - + domain: { + name: 'EAS Attestation', + version: '0.26', + chainId: 1, + verifyingContract: '0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587', + }, + primaryType: 'Attest', + message: { + schema: '0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7', + recipient: '0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2', + refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', + data: '0x0000000000000000000000000000000000000000000000000000000000000001', + time: 1699288761, + revocable: true, + expirationTime: 0, + version: 1, + }, + types: { + Attest: [ + { name: 'version', type: 'uint16' }, + { name: 'schema', type: 'bytes32' }, + { name: 'recipient', type: 'address' }, + { name: 'time', type: 'uint64' }, + { name: 'expirationTime', type: 'uint64' }, + { name: 'revocable', type: 'bool' }, + { name: 'refUID', type: 'bytes32' }, + { name: 'data', type: 'bytes' }, + ], + }, + signature: { + v: 27, + r: '0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7', + s: '0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e', + }, +} test('Encode eip712 message', async () => { const node = fromEip712(testData) @@ -99,7 +104,6 @@ test('Encode eip712 message', async () => { expect(node.attachment instanceof Uint8Array).toBeTruthy() }) - test('Canonicalize ipld eip712 object', async () => { const node = fromEip712(testData) const tape = new BytesTape(node._sig) @@ -119,22 +123,26 @@ test.skip('Generate test vectors', async () => { const signature = await walletClient.signTypedData(testData) console.log('sig', signature) - function putEntry (car, eip712, node, error) { + function putEntry(car, eip712, node, error) { const entry = { valid: error ? false : true, data: eip712 ? car.put(eip712) : null, - node: node ? car.put(node) : null + node: node ? car.put(node) : null, } if (error) entry.error = error return car.put(entry) } - const car = (new CARFactory()).build() + const car = new CARFactory().build() const entries = [] entries.push(putEntry(car, testData, fromEip712(testData))) entries.push(putEntry(car, easData, fromEip712(easData))) // invalid stuff - const invalidData1 = { ...testData, signature: "0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b" } + const invalidData1 = { + ...testData, + signature: + '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b', + } entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) const invalidNode1 = fromEip712(testData) @@ -147,12 +155,15 @@ test.skip('Generate test vectors', async () => { invalidNode3._sig.set([0x12], 3) entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) - car.put({ - canonicalization: 'eip712', - signature: 'secp256k1', - hash: 'keccak256', - entries - }, { isRoot: true }) + car.put( + { + canonicalization: 'eip712', + signature: 'secp256k1', + hash: 'keccak256', + entries, + }, + { isRoot: true } + ) - await pipeline(car, fs.createWriteStream("./eip712-secp256k1.car")); + await pipeline(car, fs.createWriteStream('./eip712-secp256k1.car')) }) From 42c2e6085e08b1276c03dd95769fb29622998b23 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 22:10:14 +0300 Subject: [PATCH 070/110] right types --- .../varsig/src/__tests__/canonicalization.test.ts | 2 +- packages/varsig/src/__tests__/eip712.test.ts | 2 +- packages/varsig/src/canonicalization.ts | 5 ++++- packages/varsig/src/canons/eip712.ts | 13 ++++++------- packages/varsig/test/canons/eip712.test.ts | 2 +- packages/varsig/test/parse-eip712.test.ts | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index ebafcade..a4b9f284 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -94,7 +94,7 @@ test('EIP712', () => { ) expect(canonicalization.kind).toEqual(CanonicalizationKind.EIP712) if (canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error() - const input = toString(canonicalization.canonicalization(TEST_DATA.message), 'hex') + const input = toString(canonicalization(TEST_DATA.message), 'hex') expect(input).toEqual('703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4') }) diff --git a/packages/varsig/src/__tests__/eip712.test.ts b/packages/varsig/src/__tests__/eip712.test.ts index 9c511240..0cfb3844 100644 --- a/packages/varsig/src/__tests__/eip712.test.ts +++ b/packages/varsig/src/__tests__/eip712.test.ts @@ -24,7 +24,7 @@ test('eip712-secp256k1.car', async () => { const node = car.get(nodeCID) const varsig = new Decoder(new BytesTape(node._sig)).read() if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - const input = varsig.canonicalization.canonicalization(data.message) + const input = varsig.canonicalization(data.message) let signature = secp256k1.Signature.fromCompact(varsig.signature) if (varsig.signing.recoveryBit) { signature = signature.addRecoveryBit(varsig.signing.recoveryBit - 27) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 888e5f6d..d7f8b760 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -11,6 +11,8 @@ export enum CanonicalizationKind { EIP191 = 0xe191, } +type IpldNode = Record + type CanonicalizationEIP191 = { kind: CanonicalizationKind.EIP191 (message: string): Uint8Array @@ -19,6 +21,7 @@ type CanonicalizationEIP191 = { type CanonicalizationEIP712 = { kind: CanonicalizationKind.EIP712 (message: any): Uint8Array + original(node: IpldNode, signature: Uint8Array, recoveryBit: number): any } export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP712 @@ -37,7 +40,7 @@ export class CanonicalizationDecoder { return Eip712.prepareCanonicalization(this.tape, hashing, sigKind) case CanonicalizationKind.EIP191: { if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) - const fn = (message: string) => { + const fn: CanonicalizationEIP191 = (message: string) => { return keccak_256( uint8arrays.fromString( `\x19Ethereum Signed Message:\n` + String(message.length) + message diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 4e7a0077..1dc36c3c 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -4,6 +4,7 @@ import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' import { hashTypedData, Hex, TypedDataDomain } from 'viem' import type { CanonicalizationAlgo } from '../canonicalization.js' +import type { SigningKind } from '../signing.js' interface Eip712Domain { name: string @@ -61,7 +62,7 @@ const SIGIL = 0xe712 export function prepareCanonicalization( tape: BytesTape, hashType: HashingAlgo, - keyType: SignatureKind + keyType: SigningKind ): CanonicalizationAlgo { if (hashType !== SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashType}`) if (!SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) @@ -73,7 +74,7 @@ export function prepareCanonicalization( primaryType, domain: decompressDomain(domain), } - const can = (node: IpldNode) => { + const canonicalization = (node: IpldNode) => { const message = ipldNodeToMessage(node) // @ts-ignore const hexHash = hashTypedData({ ...metadata, message }) @@ -86,11 +87,9 @@ export function prepareCanonicalization( const sigHex = `0x${uint8arrays.toString(sigBytes, 'base16')}` return { ...metadata, message, signature: signHex } } - return { - kind: SIGIL, - canonicalization: can, - original, - } + canonicalization.kind = SIGIL + canonicalization.original = original + return canonicalization } export const Eip712 = { SIGIL, prepareCanonicalization } diff --git a/packages/varsig/test/canons/eip712.test.ts b/packages/varsig/test/canons/eip712.test.ts index 1c7952a0..cf52406e 100644 --- a/packages/varsig/test/canons/eip712.test.ts +++ b/packages/varsig/test/canons/eip712.test.ts @@ -115,7 +115,7 @@ test('Canonicalize ipld eip712 object', async () => { const can = prepareCanonicalization(tape, 0x1b, 0xe7) expect(tape.remainder.length).toEqual(64) delete node._sig - const sigInput = can.canonicalization(node) + const sigInput = can(node) expect(sigInput).toEqual(expectedHash) }) diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/test/parse-eip712.test.ts index 47456921..4bcafced 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/test/parse-eip712.test.ts @@ -111,6 +111,6 @@ test('712 flow', async () => { ]) const decoder = new Decoder(new BytesTape(varsig)).read() if (decoder.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - const input = decoder.canonicalization.canonicalization(testData.message) + const input = decoder.canonicalization(testData.message) expect(await decoder.signing.verify(input, decoder.signature, account.address)).toBeTruthy() }) From cb66ae4b6a32103aad38719963f0cf09f3b36e9e Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Thu, 23 Nov 2023 22:10:39 +0300 Subject: [PATCH 071/110] wip --- packages/varsig/src/__tests__/eip712.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712.test.ts b/packages/varsig/src/__tests__/eip712.test.ts index 0cfb3844..9147d077 100644 --- a/packages/varsig/src/__tests__/eip712.test.ts +++ b/packages/varsig/src/__tests__/eip712.test.ts @@ -3,9 +3,8 @@ import { test } from '@jest/globals' import { CARFactory } from 'cartonne' import { Decoder } from '../decoder' import { BytesTape } from '../bytes-tape' -import {CanonicalizationKind} from "../canonicalization"; -import {decompressDomain, decompressTypes} from "../encoding/eip712"; -import {secp256k1} from "@noble/curves/secp256k1"; +import { CanonicalizationKind } from '../canonicalization' +import { secp256k1 } from '@noble/curves/secp256k1' const factory = new CARFactory() From 6c34c5cdc4a6b3c6e39a3a638912ff925450d728 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 12:10:16 +0300 Subject: [PATCH 072/110] builds --- packages/varsig/src/__tests__/eip712.test.ts | 27 ++-- packages/varsig/src/__tests__/gen-vectors.ts | 140 ++++++++++++++++++ packages/varsig/src/__tests__/signing.test.ts | 1 - packages/varsig/src/canons/eip712.ts | 59 ++++---- packages/varsig/src/signing.ts | 4 +- packages/varsig/src/signing/secp256k1.ts | 24 ++- packages/varsig/src/varsig.ts | 21 ++- 7 files changed, 214 insertions(+), 62 deletions(-) create mode 100644 packages/varsig/src/__tests__/gen-vectors.ts diff --git a/packages/varsig/src/__tests__/eip712.test.ts b/packages/varsig/src/__tests__/eip712.test.ts index 9147d077..157e728e 100644 --- a/packages/varsig/src/__tests__/eip712.test.ts +++ b/packages/varsig/src/__tests__/eip712.test.ts @@ -1,14 +1,14 @@ import { readFile } from 'node:fs/promises' import { test } from '@jest/globals' import { CARFactory } from 'cartonne' -import { Decoder } from '../decoder' -import { BytesTape } from '../bytes-tape' -import { CanonicalizationKind } from '../canonicalization' -import { secp256k1 } from '@noble/curves/secp256k1' +// import { Decoder } from '../decoder' +// import { BytesTape } from '../bytes-tape' +// import { CanonicalizationKind } from '../canonicalization' +// import { secp256k1 } from '@noble/curves/secp256k1' const factory = new CARFactory() -test('eip712-secp256k1.car', async () => { +test.skip('eip712-secp256k1.car', async () => { const carFilepath = new URL('../../test/__vectors__/eip712-secp256k1.car', import.meta.url) const carBytes = await readFile(carFilepath) const car = factory.fromBytes(carBytes) @@ -21,14 +21,15 @@ test('eip712-secp256k1.car', async () => { const nodeCID = a.node const data = car.get(dataCID) const node = car.get(nodeCID) - const varsig = new Decoder(new BytesTape(node._sig)).read() - if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - const input = varsig.canonicalization(data.message) - let signature = secp256k1.Signature.fromCompact(varsig.signature) - if (varsig.signing.recoveryBit) { - signature = signature.addRecoveryBit(varsig.signing.recoveryBit - 27) - } - console.log('pub.1', signature.recoverPublicKey(input).toHex(false)) + // const varsig = new Decoder(new BytesTape(node._sig)).read() + // if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) + // const input = varsig.canonicalization(data.message) + // let signature = secp256k1.Signature.fromCompact(varsig.signature) + // varsig.signing.verify(input, varsig.signature, ) + // if (varsig.signing.recoveryBit) { + // signature = signature.addRecoveryBit(varsig.signing.recoveryBit - 27) + // } + // console.log('pub.1', signature.recoverPublicKey(input).toHex(false)) console.log('pub.0', data, node) } } diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts new file mode 100644 index 00000000..d0c6df76 --- /dev/null +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -0,0 +1,140 @@ +import * as fs from 'node:fs' +import { pipeline } from 'node:stream/promises' +import { CARFactory, type CAR } from 'cartonne' +import { fromEip712 } from '../canons/eip712.js' +import type { CID } from 'multiformats/cid' + +const TEST_DATA = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + { name: 'attachment', type: 'bytes' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + attachment: '0xababababababababababa83459873459873459873498575986734359', + }, + signature: + '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b', +} as const + +const EAS_DATA = { + domain: { + name: 'EAS Attestation', + version: '0.26', + chainId: 1, + verifyingContract: '0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587', + }, + primaryType: 'Attest', + message: { + schema: '0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7', + recipient: '0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2', + refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', + data: '0x0000000000000000000000000000000000000000000000000000000000000001', + time: 1699288761, + revocable: true, + expirationTime: 0, + version: 1, + }, + types: { + Attest: [ + { name: 'version', type: 'uint16' }, + { name: 'schema', type: 'bytes32' }, + { name: 'recipient', type: 'address' }, + { name: 'time', type: 'uint64' }, + { name: 'expirationTime', type: 'uint64' }, + { name: 'revocable', type: 'bool' }, + { name: 'refUID', type: 'bytes32' }, + { name: 'data', type: 'bytes' }, + ], + }, + signature: { + v: 27, + r: '0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7', + s: '0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e', + }, +} + +function putEntry(car: CAR, eip712: any, node: any, error?: string): CID { + const entry: Record = { + valid: error ? false : true, + data: eip712 ? car.put(eip712) : null, + node: node ? car.put(node) : null, + } + if (error) entry['error'] = error + return car.put(entry) +} + +async function main() { + const car = new CARFactory().build() + const entries = [] + // @ts-expect-error + entries.push(putEntry(car, TEST_DATA, fromEip712(TEST_DATA))) + entries.push(putEntry(car, EAS_DATA, fromEip712(EAS_DATA))) + // invalid stuff + const invalidData1 = { + ...TEST_DATA, + signature: + '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b', + } + // @ts-expect-error + entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) + + // @ts-expect-error + const invalidNode1 = fromEip712(TEST_DATA) + invalidNode1._sig.set([0xec], 1) + entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) + // @ts-expect-error + const invalidNode2 = fromEip712(TEST_DATA) + invalidNode2._sig.set([0x00], 2) + entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) + // @ts-expect-error + const invalidNode3 = fromEip712(TEST_DATA) + invalidNode3._sig.set([0x12], 3) + entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) + + car.put( + { + canonicalization: 'eip712', + signature: 'secp256k1', + hash: 'keccak256', + entries, + }, + { isRoot: true } + ) + + await pipeline(car, fs.createWriteStream('./eip712-secp256k1.car')) +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/varsig/src/__tests__/signing.test.ts b/packages/varsig/src/__tests__/signing.test.ts index 58ad5f9d..99c0b030 100644 --- a/packages/varsig/src/__tests__/signing.test.ts +++ b/packages/varsig/src/__tests__/signing.test.ts @@ -19,7 +19,6 @@ describe('secp265k1', () => { const decoder = new SigningDecoder(tape) const result = decoder.read() expect(result.kind).toEqual(SigningKind.SECP256K1) - expect(result.recoveryBit).toEqual(undefined) }) test('readSignature', () => { const decoder = new SigningDecoder(tape) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 1dc36c3c..9009fd9e 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -40,17 +40,6 @@ export type CompressedType = Array<[string, string]> export type CompressedTypes = Record export type CompressedDomain = [string, string, number, Hex] -interface CanonicalizerResult { - digest: Uint8Array - decoded: any -} -type Canonicalization = (node: IpldNode) => Uint8Array - -interface CanonicalizerSetup { - remainder: Uint8Array - canonicalize: Canonicalize -} - const SUPPORTED_KEY_TYPES = [ 0xe7, // secp256k1 // 0x1271, // eip1271 contract signature @@ -68,10 +57,14 @@ export function prepareCanonicalization( if (!SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) const metadataLength = tape.readVarint() const metadataBytes = tape.read(metadataLength) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const [types, primaryType, domain] = JSON.parse(uint8arrays.toString(metadataBytes)) const metadata = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument types: decompressTypes(types), - primaryType, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + primaryType: primaryType, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument domain: decompressDomain(domain), } const canonicalization = (node: IpldNode) => { @@ -85,7 +78,7 @@ export function prepareCanonicalization( const sigBytes = uint8arrays.concat([signature, [recoveryBit]]) const sigHex = `0x${uint8arrays.toString(sigBytes, 'base16')}` - return { ...metadata, message, signature: signHex } + return { ...metadata, message, signature: sigHex } } canonicalization.kind = SIGIL canonicalization.original = original @@ -114,33 +107,40 @@ function ipldNodeToMessage(node: IpldNode): Record { message[key] = `0x${uint8arrays.toString(value, 'base16')}` } else if (typeof value === 'object') { // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument message[key] = ipldNodeToMessage(value) } else { // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment message[key] = value } } return message } -interface IpldEip712 { - _sig: Uint8Array - [key: string]: any +function extractSignature(signature: string | SignatureComponents) { + if (typeof signature === 'string') { + const recoveryBit = uint8arrays.fromString(signature.slice(-2), 'hex') + const signatureBytes = uint8arrays.fromString(signature.slice(2, -2), 'base16') + return { recoveryBit: recoveryBit, bytes: signatureBytes } + } else { + const recoveryBit = new Uint8Array([signature.v]) + const signatureBytes = uint8arrays.fromString( + signature.r.slice(2) + signature.s.slice(2), + 'base16' + ) + return { recoveryBit: recoveryBit, bytes: signatureBytes } + } } -export function fromEip712({ types, domain, primaryType, message, signature }: Eip712): { - node: IpldEip712 - params: Uint8Array -} { +export function fromEip712({ types, domain, primaryType, message, signature }: Eip712): IpldNode { const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] - const recoveryBit = signature.v - ? new Uint8Array([signature.v]) - : uint8arrays.fromString(signature.slice(-2), 'base16') - const signatureBytes = signature.r - ? uint8arrays.fromString(signature.r.slice(2) + signature.s.slice(2), 'base16') - : uint8arrays.fromString(signature.slice(2, -2), 'base16') + if (!signature) throw new Error(`No signature passed`) + const extracted = extractSignature(signature) + const recoveryBit = extracted.recoveryBit + const signatureBytes = extracted.bytes const varsig = uint8arrays.concat([ new Uint8Array([0x34]), // varsig sigil varintes.encode(0xe7)[0], // key type @@ -151,13 +151,13 @@ export function fromEip712({ types, domain, primaryType, message, signature }: E metadataBytes, signatureBytes, ]) + if (!message) throw new Error(`Message is required`) const node = messageToIpld(message, types, primaryType) node._sig = varsig return node } export function fromEip712A({ types, domain, primaryType, message }: Omit): { - node: IpldEip712 params: Uint8Array } { const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) @@ -171,6 +171,7 @@ export function fromEip712A({ types, domain, primaryType, message }: Omit ({ name, // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment type: FULL_TYPES[type] || type, })) } diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index ede264d7..75e24ee5 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -22,7 +22,6 @@ export type VerifySignatureFn = ( verificationKey: VerificationKey ) => Promise - export class SigningDecoder { constructor(private readonly tape: BytesTape) {} @@ -48,6 +47,9 @@ export class SigningDecoder { case SigningKind.SECP256K1: { return Secp256k1.readSignature(this.tape) } + case SigningKind.RSA: { + throw new Error(`Not supported: RSA`) + } default: throw new UnreacheableCaseError(signing.kind, 'signing kind') } diff --git a/packages/varsig/src/signing/secp256k1.ts b/packages/varsig/src/signing/secp256k1.ts index 074770c3..a5ffa56f 100644 --- a/packages/varsig/src/signing/secp256k1.ts +++ b/packages/varsig/src/signing/secp256k1.ts @@ -1,24 +1,20 @@ -import type { BytesTape } from './bytes-tape.js' import { SigningAlgo } from '../signing.js' - import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { keccak_256 } from '@noble/hashes/sha3' - - - +import type { BytesTape } from '../bytes-tape.js' const SIGIL = 0xe7 -function prepareVerifier(tape: ByteTape): SigningAlgo { +function prepareVerifier(tape: BytesTape): SigningAlgo { const recoveryBit = tape.read(1)[0] if (recoveryBit && !(recoveryBit === 27 || recoveryBit === 28)) { throw new Error(`Wrong recovery bit`) } return { kind: SIGIL, - recoveryBit: recoveryBit || undefined, - verify: async (input, signature, verificationKey) => { + // eslint-disable-next-line @typescript-eslint/require-await + verify: async (input, signature, verificationKey): Promise => { let k1Sig = secp256k1.Signature.fromCompact(signature) if (recoveryBit) { k1Sig = k1Sig.addRecoveryBit(recoveryBit - 27) @@ -28,16 +24,17 @@ function prepareVerifier(tape: ByteTape): SigningAlgo { return uint8arrays.equals(recoveredKey, verificationKey) } // convert recoveredKey to eth address and compare - else if (typeof verificationKey === 'string') { - const recoveredAddress = `0x${uint8arrays.toString( - keccak_256(recoveredKey.slice(1)) , 'base16' - ).slice(-40)}` + if (typeof verificationKey === 'string') { + const recoveredAddress = `0x${uint8arrays + .toString(keccak_256(recoveredKey.slice(1)), 'base16') + .slice(-40)}` return recoveredAddress === verificationKey.toLowerCase() } + return false } else { return secp256k1.verify(signature, input, verificationKey) } - } + }, } } @@ -45,5 +42,4 @@ function readSignature(tape: BytesTape): Uint8Array { return tape.read(64) } - export const Secp256k1 = { SIGIL, prepareVerifier, readSignature } diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 2dc9c616..e1eca8b3 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -1,3 +1,4 @@ +import { Decoder } from './decoder' interface VarsigNode { _sig: Uint8Array @@ -9,21 +10,29 @@ type PublicKey = Uint8Array type Decoded = any -export async function verify ( +export async function verify( node: VarsigNode, verificationKey: PublicKey | EthAddress ): Promise { - const { canonicalization, signing, signature } = (new Decoder(node._sig)).read() + // @ts-ignore + const { canonicalization, signing, signature } = new Decoder(node._sig).read() + // @ts-ignore delete node._sig - const signatureInput = canonicalization.canonicalization(node) + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const signatureInput = canonicalization(node) + // @ts-ignore return signing.verify(signatureInput, signature, verificationKey) - } } -export async function toOriginal (node: VarsigNode): Promise { - const { canonicalization, signing, signature } = (new Decoder(node._sig)).read() +// eslint-disable-next-line @typescript-eslint/require-await +export async function toOriginal(node: VarsigNode): Promise { + // @ts-ignore + const { canonicalization, signing, signature } = new Decoder(node._sig).read() + // @ts-ignore delete node._sig + // @ts-ignore return canonicalization.original(node, signature, signing.recoveryBig) } From 0d225b96190d68d510d9020403f5198eade61b3e Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 12:31:41 +0300 Subject: [PATCH 073/110] decompression --- packages/varsig/src/__tests__/gen-vectors.ts | 3 +- .../__tests__/vectors/eip712-secp256k1.car | Bin 0 -> 6886 bytes packages/varsig/src/canons/eip712.ts | 115 ++++++++++++++++-- 3 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 packages/varsig/src/__tests__/vectors/eip712-secp256k1.car diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts index d0c6df76..7f847543 100644 --- a/packages/varsig/src/__tests__/gen-vectors.ts +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -131,7 +131,8 @@ async function main() { { isRoot: true } ) - await pipeline(car, fs.createWriteStream('./eip712-secp256k1.car')) + const carFilepath = new URL('./vectors/eip712-secp256k1.car', import.meta.url) + await pipeline(car, fs.createWriteStream(carFilepath)) } main().catch((error) => { diff --git a/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car b/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car new file mode 100644 index 0000000000000000000000000000000000000000..b1b505deb1a2a7e6f1d94f8a8dd52ba3d373a179 GIT binary patch literal 6886 zcmeHM32+q06=i`Cz=%_2gM{&Bg>6t|*Yw;&C{yeqp(IMcfgo8N?wRS4M%vw3c1A*i z5G)nOk`3mp7(&D;nIjOH!$n~ZQzY0JWH2Dd#KBy)LI@bl4FRjtGqbyrK;?B}Tt!vX zt7>ik{{MGR_xrEs_YPT!!kT92WyeyppKRxt)Z3Yu^;X_S{rsZMYR}Slr=NCS`sqNT z;qv3x-`n+vCH=b={w}W=g>_X6I(Bt3)hBdbp4o6b^GM2zL#sZ#a5*6Z8mhUeG8y{)$TrJ^vK&Ub?)PPm0lw_nJnqwJO6lKH_G*8kr z%>n!sP!vECr3f^X6~qaQAQDI>u)f4ll*|C22nZkmL<)N#X;S7%iUvGK$PxtQg~)Mqqt7~$ljk4ou*Z=*)n$jBYe;Uc%S5;<&854>r{%iru)s~|8b3bG zW#Z3LG6NP%BW9+ip|xyB!!+iqDLAo6#O|g={9)rJbmKnYN&{)GZ;xN>s#}-mg)fdC zse5F~%CerLW()}R0T+fX+|XE>^VXN$8~0Kl_qu)hbnD*`~NXk*3dbxME(|y&6%ltaw;6=)>jdKPwLd=jgwn@c0GP0;jvrK zW!KI-xBHXFo?MW0HOM$lu99VFz!)*dV_G?=$tb2B4_Bxd`c?V*6^q1o4XwU3_M-1q zrDkcu-c1*{6<-M{qu#%{enjf>YZFoqW=BYTg)-d03~N-_K_4X~n*~j-0}-lIbSWPN zpiP1nD^sLxqlS^BhSVr!N4eMTlnoUh4i;IP-dNfe2T@5#4MPJTI-^@vz)p)b35tH( zSQa(SZ<{J%q>Rb*wlWcGZ7q4^*l13_ns4~8oc6O?!`{ciGVv_Qwob#t0ZDM_l-mtS zlJ=33AW;(QWnel>c?l!}h9^CU;khUcud!xQ;>H~H-3qXlF9~Hnx-Ih955OrA*-F!u z#0Vs2NWjnp05rw&f+S)_qzIaY0*RlLVXwhtPjy7!3zTCc5@`>X51X6(WYI#eh(trHF5na7&fQMG786W z*n^A<#RC~oJi@#UD25<709jEIS(+js1sPrf5=oH=DvT`Bq(DN2gSzR3Q%I7*mXf4M z0s$1Bk!e}tc~J(~Gh#ReP>KX-n&e0ka*W8}^9Yg<6Fw=30KwwE$<1j$5+`B``5}gar05)zGxD+4N8lXBky6D zL5l!uv9}#{Cf!n`C5QzZmSILWX&VQSX~EJ~vt?v8>xe}ZmT3`RW+dTe)VWDcX49tj z$iz%?Zy7sS#SE4R$$#7Ez)sQ4$(2=~9W6V|pYPsh8??H-QBrQyj*|0VrRdqhUtCoCc4A#6us^HeQKh(WPlY{+0J&~TpagBOHid@1C~?bUJ9iPhg9;_VZpYvr7jwc}O)-h_=u zN9Bzhb8Xz~KWHe|TvN-oPR$DZyURT%NIaTvd4j|PXygR3(|`}w>b)(}4)%KOdnQVC<_jYy5G$0<+wj}X*xI!o#*20rAgH}4=jOENW zxnHw$RDH-V$+FK%i&p>Q%^v56T}f&niZ-m9Ip_0d?yQ)Qv3bCb2M5jzZ8_@0lA}&F zUsCtos3`b!(1Ln>+k&-4=~IWEs862cpRqrA!&gT$(#rm2*x0<$5_9q3Vq?x26V!`B zAuVhm*;$B6oo3$G0(L!qwzTifg@4T$EY0*R`25YecQ($d-TZES#j@JrWB0x6bv!uO zoo&~4HS&;Ljaabjy?@)zGf$uE#MV@PSYzxtSXWp7%2RA(w{w?ocmK#e?Vqi}E*@MW zu**_)9pAw^ac-pH6spuIszwXg&8b~H@7G(`Z2jc4xU{kJseyO#)p*!!K zgRggZfUtA)Zo83tIBw*AW3nAv->-emI$uFmdD&Ax-v0Hif&)t`HYO!38|19})3OEU zx3vnpcyO^{SJa&C@}ZvJobFDxH%E{6!%MYn+Np&|l3*eJRUr>wqJ>qY&y3yq#k)|9 zDaOkiV~X+KO=61i4vLs!ywHv*#@mdhJ0Dd?QV1_dAqYt@s0CFCUu{mYF76bhhVXAX F{{fq~2tWV; literal 0 HcmV?d00001 diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 9009fd9e..808c73d4 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -182,12 +182,12 @@ export function fromEip712A({ types, domain, primaryType, message }: Omit, types: Eip712Types, - selected: string + primaryType: string ): IpldNode { const node = {} for (const [key, value] of Object.entries(message)) { // @ts-ignore - const type = types[selected].find(({ name }) => name === key).type + const type = types[primaryType].find(({ name }) => name === key).type if (!type) throw new Error(`Type for ${key} not found`) if (type.startsWith('bytes')) { // @ts-ignore @@ -246,12 +246,105 @@ function compressTypes(types: Eip712Types): CompressedTypes { return compressed } -const FULL_TYPES = { +const FULL_TYPES: Record = { u: 'uint', + u8: 'uint8', u16: 'uint16', + u24: 'uint24', + u32: 'uint32', + u40: 'uint40', + u48: 'uint48', + u56: 'uint56', u64: 'uint64', + u72: 'uint72', + u80: 'uint80', + u88: 'uint88', + u96: 'uint96', + u104: 'uint104', + u112: 'uint112', + u120: 'uint120', + u128: 'uint128', + u136: 'uint136', + u144: 'uint144', + u152: 'uint152', + u160: 'uint160', + u168: 'uint168', + u176: 'uint176', + u184: 'uint184', + u192: 'uint192', + u200: 'uint200', + u208: 'uint208', + u216: 'uint216', + u224: 'uint224', + u232: 'uint232', + u240: 'uint240', + u248: 'uint248', + u256: 'uint256', i: 'int', + i8: 'int8', + i16: 'int16', + i24: 'int24', + i32: 'int32', + i40: 'int40', + i48: 'int48', + i56: 'int56', + i64: 'int64', + i72: 'int72', + i80: 'int80', + i88: 'int88', + i96: 'int96', + i104: 'int104', + i112: 'int112', + i120: 'int120', + i128: 'int128', + i136: 'int136', + i144: 'int144', + i152: 'int152', + i160: 'int160', + i168: 'int168', + i176: 'int176', + i184: 'int184', + i192: 'int192', + i200: 'int200', + i208: 'int208', + i216: 'int216', + i224: 'int224', + i232: 'int232', + i240: 'int240', + i248: 'int248', + i256: 'int256', b: 'bytes', + b1: 'bytes1', + b2: 'bytes2', + b3: 'bytes3', + b4: 'bytes4', + b5: 'bytes5', + b6: 'bytes6', + b7: 'bytes7', + b8: 'bytes8', + b9: 'bytes9', + b10: 'bytes10', + b11: 'bytes11', + b12: 'bytes12', + b13: 'bytes13', + b14: 'bytes14', + b15: 'bytes15', + b16: 'bytes16', + b17: 'bytes17', + b18: 'bytes18', + b19: 'bytes19', + b20: 'bytes20', + b21: 'bytes21', + b22: 'bytes22', + b23: 'bytes23', + b24: 'bytes24', + b25: 'bytes25', + b26: 'bytes26', + b27: 'bytes27', + b28: 'bytes28', + b29: 'bytes29', + b30: 'bytes30', + b31: 'bytes31', b32: 'bytes32', s: 'string', a: 'address', @@ -259,15 +352,15 @@ const FULL_TYPES = { } export function decompressTypes(compressed: CompressedTypes): Eip712Types { - const types = { EIP712Domain: EIP712_DOMAIN } + const types: Eip712Types = { EIP712Domain: EIP712_DOMAIN } for (const [key, value] of Object.entries(compressed)) { - // @ts-ignore - types[key] = value.map(([name, type]) => ({ - name, - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - type: FULL_TYPES[type] || type, - })) + types[key] = value.map(([name, type]) => { + const decompressed = FULL_TYPES[type] || type + return { + name, + type: decompressed, + } + }) } return types } From 873292752f8bf3d5fcf9e2d7b33f3befd1710b20 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 12:45:08 +0300 Subject: [PATCH 074/110] decompression --- packages/varsig/src/canons/eip712.ts | 231 +++++++++++++-------------- 1 file changed, 113 insertions(+), 118 deletions(-) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 808c73d4..11f005df 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -40,6 +40,116 @@ export type CompressedType = Array<[string, string]> export type CompressedTypes = Record export type CompressedDomain = [string, string, number, Hex] +const COMPRESSED_TO_SOLIDITY: Record = { + u: 'uint', + u8: 'uint8', + u16: 'uint16', + u24: 'uint24', + u32: 'uint32', + u40: 'uint40', + u48: 'uint48', + u56: 'uint56', + u64: 'uint64', + u72: 'uint72', + u80: 'uint80', + u88: 'uint88', + u96: 'uint96', + u104: 'uint104', + u112: 'uint112', + u120: 'uint120', + u128: 'uint128', + u136: 'uint136', + u144: 'uint144', + u152: 'uint152', + u160: 'uint160', + u168: 'uint168', + u176: 'uint176', + u184: 'uint184', + u192: 'uint192', + u200: 'uint200', + u208: 'uint208', + u216: 'uint216', + u224: 'uint224', + u232: 'uint232', + u240: 'uint240', + u248: 'uint248', + u256: 'uint256', + i: 'int', + i8: 'int8', + i16: 'int16', + i24: 'int24', + i32: 'int32', + i40: 'int40', + i48: 'int48', + i56: 'int56', + i64: 'int64', + i72: 'int72', + i80: 'int80', + i88: 'int88', + i96: 'int96', + i104: 'int104', + i112: 'int112', + i120: 'int120', + i128: 'int128', + i136: 'int136', + i144: 'int144', + i152: 'int152', + i160: 'int160', + i168: 'int168', + i176: 'int176', + i184: 'int184', + i192: 'int192', + i200: 'int200', + i208: 'int208', + i216: 'int216', + i224: 'int224', + i232: 'int232', + i240: 'int240', + i248: 'int248', + i256: 'int256', + b: 'bytes', + b1: 'bytes1', + b2: 'bytes2', + b3: 'bytes3', + b4: 'bytes4', + b5: 'bytes5', + b6: 'bytes6', + b7: 'bytes7', + b8: 'bytes8', + b9: 'bytes9', + b10: 'bytes10', + b11: 'bytes11', + b12: 'bytes12', + b13: 'bytes13', + b14: 'bytes14', + b15: 'bytes15', + b16: 'bytes16', + b17: 'bytes17', + b18: 'bytes18', + b19: 'bytes19', + b20: 'bytes20', + b21: 'bytes21', + b22: 'bytes22', + b23: 'bytes23', + b24: 'bytes24', + b25: 'bytes25', + b26: 'bytes26', + b27: 'bytes27', + b28: 'bytes28', + b29: 'bytes29', + b30: 'bytes30', + b31: 'bytes31', + b32: 'bytes32', + s: 'string', + a: 'address', + o: 'bool', +} +const SOLIDITY_TO_COMPRESSED = Object.fromEntries( + Object.entries(COMPRESSED_TO_SOLIDITY).map(([k, v]) => { + return [v, k] + }) +) + const SUPPORTED_KEY_TYPES = [ 0xe7, // secp256k1 // 0x1271, // eip1271 contract signature @@ -228,134 +338,19 @@ export function decompressDomain(domain: CompressedDomain): TypedDataDomain { } function compressTypes(types: Eip712Types): CompressedTypes { - const compressed = {} + const compressed: CompressedTypes = {} for (const [key, value] of Object.entries(types)) { if (key === 'EIP712Domain') continue - // @ts-ignore - compressed[key] = value.map(({ name, type }) => [ - name, - type - .replace(/^uint(\d{1,3})$/, 'u$1') - .replace(/^int(\d{1,3})$/, 'i$1') - .replace(/^bytes(\d{0,2})$/, 'b$1') - .replace(/^string$/, 's') - .replace(/^address$/, 'a') - .replace(/^bool$/, 'o'), - ]) + compressed[key] = value.map(({ name, type }) => [name, SOLIDITY_TO_COMPRESSED[type] || type]) } return compressed } -const FULL_TYPES: Record = { - u: 'uint', - u8: 'uint8', - u16: 'uint16', - u24: 'uint24', - u32: 'uint32', - u40: 'uint40', - u48: 'uint48', - u56: 'uint56', - u64: 'uint64', - u72: 'uint72', - u80: 'uint80', - u88: 'uint88', - u96: 'uint96', - u104: 'uint104', - u112: 'uint112', - u120: 'uint120', - u128: 'uint128', - u136: 'uint136', - u144: 'uint144', - u152: 'uint152', - u160: 'uint160', - u168: 'uint168', - u176: 'uint176', - u184: 'uint184', - u192: 'uint192', - u200: 'uint200', - u208: 'uint208', - u216: 'uint216', - u224: 'uint224', - u232: 'uint232', - u240: 'uint240', - u248: 'uint248', - u256: 'uint256', - i: 'int', - i8: 'int8', - i16: 'int16', - i24: 'int24', - i32: 'int32', - i40: 'int40', - i48: 'int48', - i56: 'int56', - i64: 'int64', - i72: 'int72', - i80: 'int80', - i88: 'int88', - i96: 'int96', - i104: 'int104', - i112: 'int112', - i120: 'int120', - i128: 'int128', - i136: 'int136', - i144: 'int144', - i152: 'int152', - i160: 'int160', - i168: 'int168', - i176: 'int176', - i184: 'int184', - i192: 'int192', - i200: 'int200', - i208: 'int208', - i216: 'int216', - i224: 'int224', - i232: 'int232', - i240: 'int240', - i248: 'int248', - i256: 'int256', - b: 'bytes', - b1: 'bytes1', - b2: 'bytes2', - b3: 'bytes3', - b4: 'bytes4', - b5: 'bytes5', - b6: 'bytes6', - b7: 'bytes7', - b8: 'bytes8', - b9: 'bytes9', - b10: 'bytes10', - b11: 'bytes11', - b12: 'bytes12', - b13: 'bytes13', - b14: 'bytes14', - b15: 'bytes15', - b16: 'bytes16', - b17: 'bytes17', - b18: 'bytes18', - b19: 'bytes19', - b20: 'bytes20', - b21: 'bytes21', - b22: 'bytes22', - b23: 'bytes23', - b24: 'bytes24', - b25: 'bytes25', - b26: 'bytes26', - b27: 'bytes27', - b28: 'bytes28', - b29: 'bytes29', - b30: 'bytes30', - b31: 'bytes31', - b32: 'bytes32', - s: 'string', - a: 'address', - o: 'bool', -} - export function decompressTypes(compressed: CompressedTypes): Eip712Types { const types: Eip712Types = { EIP712Domain: EIP712_DOMAIN } for (const [key, value] of Object.entries(compressed)) { types[key] = value.map(([name, type]) => { - const decompressed = FULL_TYPES[type] || type + const decompressed = COMPRESSED_TO_SOLIDITY[type] || type return { name, type: decompressed, From 000cf85f0271436656973d6f220c0e992c28e00b Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 12:47:52 +0300 Subject: [PATCH 075/110] decompression --- packages/varsig/src/canons/eip712.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 11f005df..ecfbec1d 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -13,7 +13,7 @@ interface Eip712Domain { verifyingContract: string } -type IpldNode = Record +type IpldNode = Record & { _sig: Uint8Array } interface Eip712TypeField { name: string @@ -314,7 +314,7 @@ function messageToIpld( node[key] = value } } - return node + return node as IpldNode } const EIP712_DOMAIN = [ From baedcdba687611c348017bc408ad8fb6574a0816 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 12:51:54 +0300 Subject: [PATCH 076/110] Move tests --- .../__tests__}/__vectors__/eip712-secp256k1.car | Bin .../{test => src/__tests__}/canons/eip712.test.ts | 4 ++-- .../{test => src/__tests__}/parse-eip191.test.ts | 8 ++++---- .../{test => src/__tests__}/parse-eip712.test.ts | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) rename packages/varsig/{test => src/__tests__}/__vectors__/eip712-secp256k1.car (100%) rename packages/varsig/{test => src/__tests__}/canons/eip712.test.ts (97%) rename packages/varsig/{test => src/__tests__}/parse-eip191.test.ts (91%) rename packages/varsig/{test => src/__tests__}/parse-eip712.test.ts (91%) diff --git a/packages/varsig/test/__vectors__/eip712-secp256k1.car b/packages/varsig/src/__tests__/__vectors__/eip712-secp256k1.car similarity index 100% rename from packages/varsig/test/__vectors__/eip712-secp256k1.car rename to packages/varsig/src/__tests__/__vectors__/eip712-secp256k1.car diff --git a/packages/varsig/test/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts similarity index 97% rename from packages/varsig/test/canons/eip712.test.ts rename to packages/varsig/src/__tests__/canons/eip712.test.ts index cf52406e..a8465e0b 100644 --- a/packages/varsig/test/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -1,5 +1,5 @@ -import { fromEip712, prepareCanonicalization } from '../../src/canons/eip712.js' -import { BytesTape } from '../../src/bytes-tape.js' +import { fromEip712, prepareCanonicalization } from '../../canons/eip712.js' +import { BytesTape } from '../../bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' diff --git a/packages/varsig/test/parse-eip191.test.ts b/packages/varsig/src/__tests__/parse-eip191.test.ts similarity index 91% rename from packages/varsig/test/parse-eip191.test.ts rename to packages/varsig/src/__tests__/parse-eip191.test.ts index d3f5c16c..e81dc2b1 100644 --- a/packages/varsig/test/parse-eip191.test.ts +++ b/packages/varsig/src/__tests__/parse-eip191.test.ts @@ -3,10 +3,10 @@ import * as varintes from 'varintes' import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' -import { BytesTape } from '../src/bytes-tape.js' -import { CanonicalizationKind } from '../src/canonicalization.js' -import { Decoder } from '../src/decoder.js' -import { hex } from '../src/__tests__/hex.util.js' +import { BytesTape } from '../bytes-tape.js' +import { CanonicalizationKind } from '../canonicalization.js' +import { Decoder } from '../decoder.js' +import { hex } from './hex.util.js' test('validate eip191', async () => { const account = privateKeyToAccount( diff --git a/packages/varsig/test/parse-eip712.test.ts b/packages/varsig/src/__tests__/parse-eip712.test.ts similarity index 91% rename from packages/varsig/test/parse-eip712.test.ts rename to packages/varsig/src/__tests__/parse-eip712.test.ts index 4bcafced..ea4b04da 100644 --- a/packages/varsig/test/parse-eip712.test.ts +++ b/packages/varsig/src/__tests__/parse-eip712.test.ts @@ -3,11 +3,11 @@ import * as varintes from 'varintes' import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' -import { BytesTape } from '../src/bytes-tape.js' -import { CanonicalizationKind } from '../src/canonicalization.js' -import { fromEip712A } from '../src/canons/eip712' -import { Decoder } from '../src/decoder.js' -import { hex } from '../src/__tests__/hex.util.js' +import { BytesTape } from '../bytes-tape.js' +import { CanonicalizationKind } from '../canonicalization.js' +import { fromEip712A } from '../canons/eip712' +import { Decoder } from '../decoder.js' +import { hex } from './hex.util.js' const testData = { types: { From 18173a5989d64ed9016242bf6d18b205b796f223 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 12:52:11 +0300 Subject: [PATCH 077/110] types --- packages/varsig/src/canons/eip712.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index ecfbec1d..e76ddf23 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -13,7 +13,8 @@ interface Eip712Domain { verifyingContract: string } -type IpldNode = Record & { _sig: Uint8Array } +type IpldNode = Record +type IpldNodeSigned = IpldNode & { _sig: Uint8Array } interface Eip712TypeField { name: string @@ -243,7 +244,13 @@ function extractSignature(signature: string | SignatureComponents) { } } -export function fromEip712({ types, domain, primaryType, message, signature }: Eip712): IpldNode { +export function fromEip712({ + types, + domain, + primaryType, + message, + signature, +}: Eip712): IpldNodeSigned { const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] @@ -264,7 +271,7 @@ export function fromEip712({ types, domain, primaryType, message, signature }: E if (!message) throw new Error(`Message is required`) const node = messageToIpld(message, types, primaryType) node._sig = varsig - return node + return node as IpldNodeSigned } export function fromEip712A({ types, domain, primaryType, message }: Omit): { @@ -296,8 +303,7 @@ function messageToIpld( ): IpldNode { const node = {} for (const [key, value] of Object.entries(message)) { - // @ts-ignore - const type = types[primaryType].find(({ name }) => name === key).type + const type = types[primaryType].find(({ name }) => name === key)?.type if (!type) throw new Error(`Type for ${key} not found`) if (type.startsWith('bytes')) { // @ts-ignore From 4449703d6eeca7a80f0ca135a2d86dae6f481c88 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 13:00:29 +0300 Subject: [PATCH 078/110] Lint --- packages/varsig/src/__tests__/parse-eip191.test.ts | 1 - packages/varsig/src/__tests__/parse-eip712.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/varsig/src/__tests__/parse-eip191.test.ts b/packages/varsig/src/__tests__/parse-eip191.test.ts index e81dc2b1..df48d659 100644 --- a/packages/varsig/src/__tests__/parse-eip191.test.ts +++ b/packages/varsig/src/__tests__/parse-eip191.test.ts @@ -1,6 +1,5 @@ import { test } from '@jest/globals' import * as varintes from 'varintes' -import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../bytes-tape.js' diff --git a/packages/varsig/src/__tests__/parse-eip712.test.ts b/packages/varsig/src/__tests__/parse-eip712.test.ts index ea4b04da..cfa42643 100644 --- a/packages/varsig/src/__tests__/parse-eip712.test.ts +++ b/packages/varsig/src/__tests__/parse-eip712.test.ts @@ -1,6 +1,5 @@ import { test } from '@jest/globals' import * as varintes from 'varintes' -import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../bytes-tape.js' From 4d51cdba883cbcfd9c8e4aae174e46413be8d085 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 13:05:33 +0300 Subject: [PATCH 079/110] wip --- .../src/__tests__/canons/eip712.test.ts | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/varsig/src/__tests__/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts index a8465e0b..de33f590 100644 --- a/packages/varsig/src/__tests__/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -1,18 +1,15 @@ import { fromEip712, prepareCanonicalization } from '../../canons/eip712.js' import { BytesTape } from '../../bytes-tape.js' import * as uint8arrays from 'uint8arrays' -import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import * as fs from 'node:fs' import { pipeline } from 'node:stream/promises' -import { CARFactory, CarBlock } from 'cartonne' +import { CARFactory, type CAR } from 'cartonne' +import { CanonicalizationKind } from '../../canonicalization.js' -const walletClient = createWalletClient({ - account: privateKeyToAccount( - '0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504' - ), - transport: custom({ request: async () => {} }), -}) +const account = privateKeyToAccount( + '0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504' +) const testData = { types: { @@ -54,7 +51,8 @@ const testData = { }, signature: '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b', -} +} as const + const expectedHash = uint8arrays.fromString( '703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', 'base16' @@ -97,14 +95,16 @@ const easData = { }, } -test('Encode eip712 message', async () => { +test('Encode eip712 message', () => { + // @ts-ignore const node = fromEip712(testData) expect(node._sig.length).toEqual(268) expect(node.attachment instanceof Uint8Array).toBeTruthy() }) -test('Canonicalize ipld eip712 object', async () => { +test('Canonicalize ipld eip712 object', () => { + // @ts-ignore const node = fromEip712(testData) const tape = new BytesTape(node._sig) tape.readVarint() // skip sigil @@ -113,18 +113,21 @@ test('Canonicalize ipld eip712 object', async () => { tape.readVarint() // skip hash type tape.readVarint() // skip canonicalizer codec const can = prepareCanonicalization(tape, 0x1b, 0xe7) + if (can.kind !== CanonicalizationKind.EIP712) throw new Error(`Nope`) expect(tape.remainder.length).toEqual(64) + // @ts-ignore delete node._sig const sigInput = can(node) expect(sigInput).toEqual(expectedHash) }) test.skip('Generate test vectors', async () => { - const signature = await walletClient.signTypedData(testData) + // @ts-ignore + const signature = await account.signTypedData(testData) console.log('sig', signature) - function putEntry(car, eip712, node, error) { - const entry = { + function putEntry(car: CAR, eip712: any, node: any, error?: string) { + const entry: Record = { valid: error ? false : true, data: eip712 ? car.put(eip712) : null, node: node ? car.put(node) : null, @@ -135,7 +138,9 @@ test.skip('Generate test vectors', async () => { const car = new CARFactory().build() const entries = [] + // @ts-ignore entries.push(putEntry(car, testData, fromEip712(testData))) + // @ts-ignore entries.push(putEntry(car, easData, fromEip712(easData))) // invalid stuff const invalidData1 = { @@ -143,14 +148,18 @@ test.skip('Generate test vectors', async () => { signature: '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b', } + // @ts-ignore entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) + // @ts-ignore const invalidNode1 = fromEip712(testData) invalidNode1._sig.set([0xec], 1) entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) + // @ts-ignore const invalidNode2 = fromEip712(testData) invalidNode2._sig.set([0x00], 2) entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) + // @ts-ignore const invalidNode3 = fromEip712(testData) invalidNode3._sig.set([0x12], 3) entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) From 23200a9d7758a009a2c53774c4c31613e5a90c06 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 13:06:47 +0300 Subject: [PATCH 080/110] eip712 test ok --- packages/varsig/src/__tests__/canons/eip712.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/varsig/src/__tests__/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts index de33f590..bbe84cd0 100644 --- a/packages/varsig/src/__tests__/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -128,7 +128,7 @@ test.skip('Generate test vectors', async () => { function putEntry(car: CAR, eip712: any, node: any, error?: string) { const entry: Record = { - valid: error ? false : true, + valid: !error, data: eip712 ? car.put(eip712) : null, node: node ? car.put(node) : null, } From 58013107c1be950b1efebdfb0d71d0c8db688735 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 13:17:16 +0300 Subject: [PATCH 081/110] gen-vectors 2 --- packages/varsig/src/__tests__/gen-vectors.ts | 70 +++++++++++++++--- .../__tests__/vectors/eip712-secp256k1.car | Bin 6886 -> 7341 bytes packages/varsig/src/canons/eip712.ts | 2 + 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts index 7f847543..5ba72121 100644 --- a/packages/varsig/src/__tests__/gen-vectors.ts +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -1,8 +1,9 @@ import * as fs from 'node:fs' import { pipeline } from 'node:stream/promises' import { CARFactory, type CAR } from 'cartonne' -import { fromEip712 } from '../canons/eip712.js' +import { fromEip712, Signer } from '../canons/eip712.js' import type { CID } from 'multiformats/cid' +import { privateKeyToAccount } from 'viem/accounts' const TEST_DATA = { types: { @@ -83,11 +84,16 @@ const EAS_DATA = { }, } -function putEntry(car: CAR, eip712: any, node: any, error?: string): CID { +const ACCOUNT = privateKeyToAccount( + '0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504' +) + +function putEntry(car: CAR, eip712: any, node: any, signer: Signer, error?: string): CID { const entry: Record = { - valid: error ? false : true, + valid: !error, data: eip712 ? car.put(eip712) : null, node: node ? car.put(node) : null, + signer: signer, } if (error) entry['error'] = error return car.put(entry) @@ -97,29 +103,73 @@ async function main() { const car = new CARFactory().build() const entries = [] // @ts-expect-error - entries.push(putEntry(car, TEST_DATA, fromEip712(TEST_DATA))) - entries.push(putEntry(car, EAS_DATA, fromEip712(EAS_DATA))) + entries.push(putEntry(car, TEST_DATA, fromEip712(TEST_DATA), { publicKey: ACCOUNT.publicKey })) + entries.push( + putEntry(car, EAS_DATA, fromEip712(EAS_DATA), { + address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', + }) + ) // invalid stuff const invalidData1 = { ...TEST_DATA, signature: '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b', } - // @ts-expect-error - entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) + entries.push( + putEntry( + car, + invalidData1, + // @ts-expect-error + fromEip712(invalidData1), + { + address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', + }, + 'Invalid signature' + ) + ) // @ts-expect-error const invalidNode1 = fromEip712(TEST_DATA) invalidNode1._sig.set([0xec], 1) - entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) + entries.push( + putEntry( + car, + null, + invalidNode1, + { + address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', + }, + 'Unsupported key type' + ) + ) // @ts-expect-error const invalidNode2 = fromEip712(TEST_DATA) invalidNode2._sig.set([0x00], 2) - entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) + entries.push( + putEntry( + car, + null, + invalidNode2, + { + address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', + }, + 'Missing recovery bit' + ) + ) // @ts-expect-error const invalidNode3 = fromEip712(TEST_DATA) invalidNode3._sig.set([0x12], 3) - entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) + entries.push( + putEntry( + car, + null, + invalidNode3, + { + address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', + }, + 'Unsupported hash type' + ) + ) car.put( { diff --git a/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car b/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car index b1b505deb1a2a7e6f1d94f8a8dd52ba3d373a179..635593f017ad11c99217a739c7c0853033c01e1c 100644 GIT binary patch delta 1126 zcmaE6y4KRjYEf!Yett=D;|;9{RR+dFA%(d&o^XHMTiEbos!Gd3_cNlh_LNinuaG&M^y zH8Nc;>b_+1eePsC zm`~CZQ&Ngji;F9?3@U(5HgqyEvotp`Fmz3EHZo6iGBP)`baOLFF;6seG&eUkcQLYX zHr~fJd7Yrj<_sQDR`okzJ0IPSPOoq666ut_T7ALgK#y&G*0IitDKjtqJ29tYWtrB} z$x8&QZ78vGv%Ju6M)mDr+w@+jzk4g9Tlu1Imgg(e4Ga&X7w~WfJbY@(Vq^}~%t9|j^HD|(q9^i~U7C58s{o9Fd@3%j>=?t<>Wb$b- zC(3N(6$je51MI(~^{?Md=VuJBFZ+1OHIbvmJd?lh=iV;c_hCnScGP6-hS(@9t}r=C zQaOo|P`wXM`-HN|K%+ArE?8#OR;fIzrS68>&`b`@gH^(>nFZEa9vz=vef51Q^xy? zC(mU~X8Oc7xsgX@^FFpoFnwH|{mLt2GjopeZoC|r9uxW{rjPY$Q@&DlsGRwrEXn)C}JPah(@UT_fiiPB=dMNA1$XCZCx)4IV|LoG{eiUyCPkf({f6B?T5?mI*qSNmP`&54cq)! z6sBJ2)Xu$+)L*ihE$uwJwB*paGiR=}tC{`ddHL}t-vy`I7a;W_lO-jbHcyuT>Zt+Q zVRl85Y3VB0thr5%YTCR1{>VB#yJMpi?|cn~GwbG0e!UZ-=d6^o+I=Qs!gX?yv{F5x l5QWDQp&*1SBouUTg`hCY!xw09m4reIu8>d=O>UIY0RZ!pB830| diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index e76ddf23..85b650b7 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -365,3 +365,5 @@ export function decompressTypes(compressed: CompressedTypes): Eip712Types { } return types } + +export type Signer = { publicKey: Uint8Array } | { address: Hex } From 64c8fedcdb32420f8a116e95812c2231b4a02796 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 13:25:35 +0300 Subject: [PATCH 082/110] Full api --- .../src/__tests__/canonicalization.test.ts | 14 +++++-- .../src/__tests__/canons/eip712.test.ts | 22 +++++------ .../varsig/src/__tests__/parse-eip712.test.ts | 19 ++-------- packages/varsig/src/canons/eip712.ts | 38 +------------------ packages/varsig/src/varsig.ts | 2 + 5 files changed, 29 insertions(+), 66 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index a4b9f284..9398eca8 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -5,7 +5,7 @@ import { concat, toString } from 'uint8arrays' import { encode } from 'varintes/encode' import { HashingAlgo } from '../hashing.js' import { SigningKind } from '../signing.js' -import { fromEip712A } from '../canons/eip712.js' +import { fromOriginal } from '../canons/eip712.js' const TEST_DATA = { types: { @@ -75,18 +75,24 @@ const TEST_DATA = { contents: 'Hello, Bob!', attachment: '0xababababababababababa83459873459873459873498575986734359', }, + signature: + '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e11b3e47242d82341870dc31fbc2146d1b', } as const test('EIP712', () => { - const a = fromEip712A({ + const node = fromOriginal({ // @ts-ignore types: TEST_DATA.types, domain: TEST_DATA.domain, primaryType: TEST_DATA.primaryType, message: TEST_DATA.message, + signature: TEST_DATA.signature, }) - const bytes = concat([encode(0xe712)[0], a.params]) - const tape = new BytesTape(bytes) + const tape = new BytesTape(node._sig) + tape.readVarint() // skip varsig sigil + tape.readVarint() // skip key sigil + tape.read(1) + tape.readVarint() // skip hash sigil const canonicalization = CanonicalizationDecoder.read( tape, HashingAlgo.KECCAK256, diff --git a/packages/varsig/src/__tests__/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts index bbe84cd0..cef2f30a 100644 --- a/packages/varsig/src/__tests__/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -1,4 +1,4 @@ -import { fromEip712, prepareCanonicalization } from '../../canons/eip712.js' +import { fromOriginal, prepareCanonicalization } from '../../canons/eip712.js' import { BytesTape } from '../../bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' @@ -95,17 +95,17 @@ const easData = { }, } -test('Encode eip712 message', () => { +test('Encode eip712 message', async () => { // @ts-ignore - const node = fromEip712(testData) + const node = fromOriginal(testData) expect(node._sig.length).toEqual(268) expect(node.attachment instanceof Uint8Array).toBeTruthy() }) -test('Canonicalize ipld eip712 object', () => { +test('Canonicalize ipld eip712 object', async () => { // @ts-ignore - const node = fromEip712(testData) + const node = fromOriginal(testData) const tape = new BytesTape(node._sig) tape.readVarint() // skip sigil tape.readVarint() // skip key type @@ -139,9 +139,9 @@ test.skip('Generate test vectors', async () => { const car = new CARFactory().build() const entries = [] // @ts-ignore - entries.push(putEntry(car, testData, fromEip712(testData))) + entries.push(putEntry(car, testData, fromOriginal(testData))) // @ts-ignore - entries.push(putEntry(car, easData, fromEip712(easData))) + entries.push(putEntry(car, easData, fromOriginal(easData))) // invalid stuff const invalidData1 = { ...testData, @@ -149,18 +149,18 @@ test.skip('Generate test vectors', async () => { '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b', } // @ts-ignore - entries.push(putEntry(car, invalidData1, fromEip712(invalidData1), 'Invalid signature')) + entries.push(putEntry(car, invalidData1, fromOriginal(invalidData1), 'Invalid signature')) // @ts-ignore - const invalidNode1 = fromEip712(testData) + const invalidNode1 = fromOriginal(testData) invalidNode1._sig.set([0xec], 1) entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) // @ts-ignore - const invalidNode2 = fromEip712(testData) + const invalidNode2 = fromOriginal(testData) invalidNode2._sig.set([0x00], 2) entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) // @ts-ignore - const invalidNode3 = fromEip712(testData) + const invalidNode3 = fromOriginal(testData) invalidNode3._sig.set([0x12], 3) entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) diff --git a/packages/varsig/src/__tests__/parse-eip712.test.ts b/packages/varsig/src/__tests__/parse-eip712.test.ts index cfa42643..6381a25c 100644 --- a/packages/varsig/src/__tests__/parse-eip712.test.ts +++ b/packages/varsig/src/__tests__/parse-eip712.test.ts @@ -4,7 +4,7 @@ import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../bytes-tape.js' import { CanonicalizationKind } from '../canonicalization.js' -import { fromEip712A } from '../canons/eip712' +import { fromOriginal } from '../canons/eip712' import { Decoder } from '../decoder.js' import { hex } from './hex.util.js' @@ -88,26 +88,15 @@ test('712 flow', async () => { primaryType: testData.primaryType, message: testData.message, }) - const signatureBytes = uint8arrays.fromString( - stringSignature.toLowerCase().replace(/^0x/, ''), - 'hex' - ) - const a = fromEip712A({ + const node = fromOriginal({ // @ts-ignore types: testData.types, domain: testData.domain, primaryType: testData.primaryType, message: testData.message, + signature: stringSignature }) - const varsig = uint8arrays.concat([ - hex(0x34), - varintes.encode(0xe7)[0], - signatureBytes.subarray(64), - varintes.encode(0x1b)[0], - varintes.encode(CanonicalizationKind.EIP712)[0], - a.params, - signatureBytes.subarray(0, 64), - ]) + const varsig = node._sig const decoder = new Decoder(new BytesTape(varsig)).read() if (decoder.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) const input = decoder.canonicalization(testData.message) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 85b650b7..724b800a 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -196,19 +196,7 @@ export function prepareCanonicalization( return canonicalization } -export const Eip712 = { SIGIL, prepareCanonicalization } - -// function parameterizeCanonicalizer({ types, primaryType, domain }: Eip712): Canonicalize { -// return (node: IpldNode) => { -// const message = ipldNodeToMessage(node) -// // @ts-ignore -// const hexHash = hashTypedData({ types, primaryType, domain, message }) -// return { -// digest: uint8arrays.fromString(hexHash.slice(2), 'base16'), -// decoded: { types, primaryType, domain, message }, -// } -// } -// } +export const Eip712 = { SIGIL, prepareCanonicalization, fromOriginal } function ipldNodeToMessage(node: IpldNode): Record { const message = {} @@ -244,7 +232,7 @@ function extractSignature(signature: string | SignatureComponents) { } } -export function fromEip712({ +export function fromOriginal({ types, domain, primaryType, @@ -274,28 +262,6 @@ export function fromEip712({ return node as IpldNodeSigned } -export function fromEip712A({ types, domain, primaryType, message }: Omit): { - params: Uint8Array -} { - const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) - const metadataBytes = uint8arrays.fromString(metadata) - const metadataLength = varintes.encode(metadataBytes.length)[0] - const varsig = uint8arrays.concat([ - // new Uint8Array([0x34]), // varsig sigil - // varintes.encode(0xe7)[0], // key type - // varintes.encode(0x1b)[0], // hash type - // varintes.encode(0xe712)[0], // canonicalizer codec - metadataLength, - metadataBytes, - ]) - if (!message) throw new Error(`No message passsed`) - const node = messageToIpld(message, types, primaryType) - node._sig = varsig - return { - params: varsig, - } -} - function messageToIpld( message: Record, types: Eip712Types, diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index e1eca8b3..314657a0 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -1,5 +1,7 @@ import { Decoder } from './decoder' +export { Eip712 } from './canons/eip712' + interface VarsigNode { _sig: Uint8Array [key: string]: any From 158ad761902707fd172dc6190809a23e6d4013ce Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 14:05:40 +0300 Subject: [PATCH 083/110] wip --- .../src/__tests__/canons/eip712.test.ts | 101 ------------------ .../src/__tests__/eip712-secp256k1.test.ts | 60 +++++++++++ packages/varsig/src/__tests__/eip712.test.ts | 36 ------- packages/varsig/src/__tests__/gen-vectors.ts | 2 +- .../__tests__/vectors/eip712-secp256k1.car | Bin 7341 -> 7341 bytes 5 files changed, 61 insertions(+), 138 deletions(-) create mode 100644 packages/varsig/src/__tests__/eip712-secp256k1.test.ts delete mode 100644 packages/varsig/src/__tests__/eip712.test.ts diff --git a/packages/varsig/src/__tests__/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts index cef2f30a..b36c89df 100644 --- a/packages/varsig/src/__tests__/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -1,16 +1,8 @@ import { fromOriginal, prepareCanonicalization } from '../../canons/eip712.js' import { BytesTape } from '../../bytes-tape.js' import * as uint8arrays from 'uint8arrays' -import { privateKeyToAccount } from 'viem/accounts' -import * as fs from 'node:fs' -import { pipeline } from 'node:stream/promises' -import { CARFactory, type CAR } from 'cartonne' import { CanonicalizationKind } from '../../canonicalization.js' -const account = privateKeyToAccount( - '0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504' -) - const testData = { types: { EIP712Domain: [ @@ -58,43 +50,6 @@ const expectedHash = uint8arrays.fromString( 'base16' ) -const easData = { - domain: { - name: 'EAS Attestation', - version: '0.26', - chainId: 1, - verifyingContract: '0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587', - }, - primaryType: 'Attest', - message: { - schema: '0xc59265615401143689cbfe73046a922c975c99d97e4c248070435b1104b2dea7', - recipient: '0x17640d0D8C93bF710b6Ee4208997BB727B5B7bc2', - refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', - data: '0x0000000000000000000000000000000000000000000000000000000000000001', - time: 1699288761, - revocable: true, - expirationTime: 0, - version: 1, - }, - types: { - Attest: [ - { name: 'version', type: 'uint16' }, - { name: 'schema', type: 'bytes32' }, - { name: 'recipient', type: 'address' }, - { name: 'time', type: 'uint64' }, - { name: 'expirationTime', type: 'uint64' }, - { name: 'revocable', type: 'bool' }, - { name: 'refUID', type: 'bytes32' }, - { name: 'data', type: 'bytes' }, - ], - }, - signature: { - v: 27, - r: '0x65f777899dddd381d138eb0e1350071a6bcb6430a3a58c1c232eaf5db4292af7', - s: '0x7f225138ccfc901f85d4dc88bd199de57f13fc144272ba75b5459a2a14629b1e', - }, -} - test('Encode eip712 message', async () => { // @ts-ignore const node = fromOriginal(testData) @@ -120,59 +75,3 @@ test('Canonicalize ipld eip712 object', async () => { const sigInput = can(node) expect(sigInput).toEqual(expectedHash) }) - -test.skip('Generate test vectors', async () => { - // @ts-ignore - const signature = await account.signTypedData(testData) - console.log('sig', signature) - - function putEntry(car: CAR, eip712: any, node: any, error?: string) { - const entry: Record = { - valid: !error, - data: eip712 ? car.put(eip712) : null, - node: node ? car.put(node) : null, - } - if (error) entry.error = error - return car.put(entry) - } - - const car = new CARFactory().build() - const entries = [] - // @ts-ignore - entries.push(putEntry(car, testData, fromOriginal(testData))) - // @ts-ignore - entries.push(putEntry(car, easData, fromOriginal(easData))) - // invalid stuff - const invalidData1 = { - ...testData, - signature: - '0x0c095239e4d3d2cc0b7aa28110f42abcdefe47656bbde7048244471e701331ec3f94adfe7959b0ed0efec533d511f9e1e1187623487682341870dc31fbc2146d1b', - } - // @ts-ignore - entries.push(putEntry(car, invalidData1, fromOriginal(invalidData1), 'Invalid signature')) - - // @ts-ignore - const invalidNode1 = fromOriginal(testData) - invalidNode1._sig.set([0xec], 1) - entries.push(putEntry(car, null, invalidNode1, 'Unsupported key type')) - // @ts-ignore - const invalidNode2 = fromOriginal(testData) - invalidNode2._sig.set([0x00], 2) - entries.push(putEntry(car, null, invalidNode2, 'Missing recovery bit')) - // @ts-ignore - const invalidNode3 = fromOriginal(testData) - invalidNode3._sig.set([0x12], 3) - entries.push(putEntry(car, null, invalidNode3, 'Unsupported hash type')) - - car.put( - { - canonicalization: 'eip712', - signature: 'secp256k1', - hash: 'keccak256', - entries, - }, - { isRoot: true } - ) - - await pipeline(car, fs.createWriteStream('./eip712-secp256k1.car')) -}) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts new file mode 100644 index 00000000..072fc6b1 --- /dev/null +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -0,0 +1,60 @@ +import { readFile } from 'node:fs/promises' +import { expect, test } from '@jest/globals' +import { CARFactory } from 'cartonne' +import { CID } from 'multiformats/cid' +import { BytesTape } from '../bytes-tape' +import { Decoder } from '../decoder' +import * as uint8arrays from 'uint8arrays' +import { CanonicalizationKind } from '../canonicalization.js' + +const factory = new CARFactory() + +test('eip712-secp256k1.car', async () => { + const carFilepath = new URL('./vectors/eip712-secp256k1.car', import.meta.url) + const carBytes = await readFile(carFilepath) + const car = factory.fromBytes(carBytes) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const root = car.get(car.roots[0]) + if (!root) throw new Error(`Empty root`) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const entries = root.entries as Array + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + if (!entry['data']) continue + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const data = car.get(entry.data) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const node = car.get(entry.node) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const varsig = new Decoder(new BytesTape(node._sig)).read() + if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const input = varsig.canonicalization(data.message) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const signer = entry.signer + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + if (signer.publicKey) { + const verificationResult = await varsig.signing.verify( + input, + varsig.signature, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + uint8arrays.fromString(signer.publicKey.replace(/^0x/, ''), 'hex') + ) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,jest/no-conditional-expect + expect(verificationResult).toEqual(entry.valid) + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (signer.address) { + const verificationResult = await varsig.signing.verify( + input, + varsig.signature, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access + signer.address + ) + // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-member-access + expect(verificationResult).toEqual(entry.valid) + } + } +}) diff --git a/packages/varsig/src/__tests__/eip712.test.ts b/packages/varsig/src/__tests__/eip712.test.ts deleted file mode 100644 index 157e728e..00000000 --- a/packages/varsig/src/__tests__/eip712.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { readFile } from 'node:fs/promises' -import { test } from '@jest/globals' -import { CARFactory } from 'cartonne' -// import { Decoder } from '../decoder' -// import { BytesTape } from '../bytes-tape' -// import { CanonicalizationKind } from '../canonicalization' -// import { secp256k1 } from '@noble/curves/secp256k1' - -const factory = new CARFactory() - -test.skip('eip712-secp256k1.car', async () => { - const carFilepath = new URL('../../test/__vectors__/eip712-secp256k1.car', import.meta.url) - const carBytes = await readFile(carFilepath) - const car = factory.fromBytes(carBytes) - const root = car.get(car.roots[0]) - if (!root) throw new Error(`Empty root`) - for (const entryCID of root.entries) { - const a = car.get(entryCID) - if (a.valid) { - const dataCID = a.data - const nodeCID = a.node - const data = car.get(dataCID) - const node = car.get(nodeCID) - // const varsig = new Decoder(new BytesTape(node._sig)).read() - // if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - // const input = varsig.canonicalization(data.message) - // let signature = secp256k1.Signature.fromCompact(varsig.signature) - // varsig.signing.verify(input, varsig.signature, ) - // if (varsig.signing.recoveryBit) { - // signature = signature.addRecoveryBit(varsig.signing.recoveryBit - 27) - // } - // console.log('pub.1', signature.recoverPublicKey(input).toHex(false)) - console.log('pub.0', data, node) - } - } -}) diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts index 5ba72121..47180039 100644 --- a/packages/varsig/src/__tests__/gen-vectors.ts +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -106,7 +106,7 @@ async function main() { entries.push(putEntry(car, TEST_DATA, fromEip712(TEST_DATA), { publicKey: ACCOUNT.publicKey })) entries.push( putEntry(car, EAS_DATA, fromEip712(EAS_DATA), { - address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', + address: '0x3e95B8E249c4536FE1db2E4ce5476010767C0A05', }) ) // invalid stuff diff --git a/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car b/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car index 635593f017ad11c99217a739c7c0853033c01e1c..3c3041a046b6beaf621752881f63b7452f680eb1 100644 GIT binary patch delta 216 zcmZ2$xz^IiYEf!Yett=D;|;9{RR+dFAq7_du!*%zp^kgCzZ`wVZ}2&3AvzKf9c1#%ATK2aiuK Date: Fri, 24 Nov 2023 14:37:51 +0300 Subject: [PATCH 084/110] npm run gen:vectors --- packages/varsig/package.json | 3 ++- packages/varsig/src/__tests__/gen-vectors.ts | 25 ++++++++++++------ .../__tests__/vectors/eip712-secp256k1.car | Bin 7341 -> 7365 bytes 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 45b1e594..0128774d 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -27,7 +27,8 @@ "prepare": "pnpm run build", "prepublishOnly": "package-check", "size": "./node_modules/.bin/size-limit", - "analyze": "./node_modules/.bin/size-limit --why" + "analyze": "./node_modules/.bin/size-limit --why", + "gen:vectors": "./node_modules/.bin/tsm ./src/__tests__/gen-vectors.ts" }, "repository": { "type": "git", diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts index 47180039..40f8c396 100644 --- a/packages/varsig/src/__tests__/gen-vectors.ts +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -1,7 +1,7 @@ import * as fs from 'node:fs' import { pipeline } from 'node:stream/promises' import { CARFactory, type CAR } from 'cartonne' -import { fromEip712, Signer } from '../canons/eip712.js' +import { fromOriginal, Signer } from '../canons/eip712.js' import type { CID } from 'multiformats/cid' import { privateKeyToAccount } from 'viem/accounts' @@ -91,7 +91,7 @@ const ACCOUNT = privateKeyToAccount( function putEntry(car: CAR, eip712: any, node: any, signer: Signer, error?: string): CID { const entry: Record = { valid: !error, - data: eip712 ? car.put(eip712) : null, + original: eip712 ? car.put(eip712) : null, node: node ? car.put(node) : null, signer: signer, } @@ -103,9 +103,11 @@ async function main() { const car = new CARFactory().build() const entries = [] // @ts-expect-error - entries.push(putEntry(car, TEST_DATA, fromEip712(TEST_DATA), { publicKey: ACCOUNT.publicKey })) + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + entries.push(putEntry(car, TEST_DATA, fromOriginal(TEST_DATA), { publicKey: ACCOUNT.publicKey })) entries.push( - putEntry(car, EAS_DATA, fromEip712(EAS_DATA), { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + putEntry(car, EAS_DATA, fromOriginal(EAS_DATA), { address: '0x3e95B8E249c4536FE1db2E4ce5476010767C0A05', }) ) @@ -120,7 +122,8 @@ async function main() { car, invalidData1, // @ts-expect-error - fromEip712(invalidData1), + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + fromOriginal(invalidData1), { address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', }, @@ -129,7 +132,9 @@ async function main() { ) // @ts-expect-error - const invalidNode1 = fromEip712(TEST_DATA) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call + const invalidNode1 = fromOriginal(TEST_DATA) + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access invalidNode1._sig.set([0xec], 1) entries.push( putEntry( @@ -143,7 +148,9 @@ async function main() { ) ) // @ts-expect-error - const invalidNode2 = fromEip712(TEST_DATA) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call + const invalidNode2 = fromOriginal(TEST_DATA) + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access invalidNode2._sig.set([0x00], 2) entries.push( putEntry( @@ -157,7 +164,9 @@ async function main() { ) ) // @ts-expect-error - const invalidNode3 = fromEip712(TEST_DATA) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call + const invalidNode3 = fromOriginal(TEST_DATA) + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access invalidNode3._sig.set([0x12], 3) entries.push( putEntry( diff --git a/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car b/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car index 3c3041a046b6beaf621752881f63b7452f680eb1..87c4b882bb7dfab30f59ff98abcc08b0a370e06b 100644 GIT binary patch delta 719 zcmZ2$dDPO#YEf!Yett=D;|;9{RR+dFA%zFECo^9@DETGzwdly{JC(|9+nrvisjsW) zG2~?1pLP4$Mk5zi^(9OorNy^=WP=vmviP4btR~yywe-r-sKVwPU%e>aeY<2|JbS)m z@@dwyd>Q#gndzB%i8){^CfcenePWy3$fL4(AKN5W^%IOBljg4}e7%0sqUxj`C*z+F z{i1i=y#4w4Wv-sT{kIocObtp{GI=RaAt&6V$u&Hho5gsoS=AqawZB*N+;R89gDY+3 z6Q*+T zNN;1xysO5QhqkKz-~LQ?eogI*=-EHnrIt<(7wzGK+x~6yV^O%?E{FG$Id>OXgssh} ziCV7P8uc#O+hx9#+OIs-GnZ-0`lqnl93!xa(=W4J<4 XXy)MyPPj@!VFy=8D4-@c%IE+9Msr9X delta 717 zcmX?Vxz^IiYEf!Yett=D;|;9{RR+dFAq7_du!*%zp^kgCzZ`wVZT6UAMVx;x*tl2tBIBe9Ut;xqbJPnS<(4l= zNl7e81e-k3R%P;B*0Y=Eu}2&3AvzKf9c1 z#%ATK2ahA^o;;6DWpXu7;bu8rYgWTMVC|1?N2k}fc8PRKU#-62a-heyKI>R##gv(s z{+*c9v9e5SDZ=u}3cMRw1?>@^JIAmmCcf3S7Evr%=>l!NMA*iSNr0-YtDrKJir-y zEO18M`?nL_-fw?$(;22)RHA3|Y6+mFJHVDEt$+PyIzMB0ec8uLu8ABi=9&D3KlgUo zz7IRvv!fVJ1O;O~zHo%ABot(D dg@ghrvp6$7FR`SwC^fS Date: Fri, 24 Nov 2023 15:22:30 +0300 Subject: [PATCH 085/110] gen-vector! --- packages/varsig/package.json | 4 +++- .../__vectors__/eip712-secp256k1.car | Bin 6886 -> 7365 bytes .../src/__tests__/eip712-secp256k1.test.ts | 11 +++++++---- packages/varsig/src/__tests__/gen-vectors.ts | 2 +- .../varsig/src/__tests__/parse-eip712.test.ts | 5 +---- .../__tests__/vectors/eip712-secp256k1.car | Bin 7365 -> 0 bytes packages/varsig/src/canons/eip712.ts | 5 +++-- pnpm-lock.yaml | 6 ++++++ 8 files changed, 21 insertions(+), 12 deletions(-) delete mode 100644 packages/varsig/src/__tests__/vectors/eip712-secp256k1.car diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 0128774d..9f0a240d 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -47,13 +47,15 @@ "homepage": "https://github.com/ceramicnetwork/js-did#readme", "devDependencies": { "@stablelib/random": "^1.0.2", - "cartonne": "^2.2.0" + "cartonne": "^2.2.0", + "tsm": "^2.3.0" }, "dependencies": { "@noble/curves": "^1.2.0", "@noble/hashes": "^1.3.2", "@types/node": "^20.2.3", "codeco": "^1.1.0", + "fast-json-stable-stringify": "^2.1.0", "key-did-provider-ed25519": "workspace:^", "key-did-resolver": "workspace:^", "multiformats": "^11.0.2", diff --git a/packages/varsig/src/__tests__/__vectors__/eip712-secp256k1.car b/packages/varsig/src/__tests__/__vectors__/eip712-secp256k1.car index b1b505deb1a2a7e6f1d94f8a8dd52ba3d373a179..e22734c8161cc6f9f99480d144051fd5ce72e6c0 100644 GIT binary patch delta 1731 zcmaE6deqX$YEf!Yett=D;|;9{RR+dFA%&At`*&=*?_{g^@{XF*e`TLE^>tqEO;e9u zy8UJHi6V{UjYj^=3jZS)A2(y*U%c@`#I`LiPrZJ=U-r@C@NHA)%qi@^`yUv3gr3Wwq5*_v7x3W+i8i z6>s;A{j?+{FFz#}?jS;TrIsb;WTt#gE6z;MOD$TMSx}mklbP(DT3OLzP+?$VX>4Sc zY-wm_X_#7Xkz|mRVrph?Zee7aVq|QZm}F^UY+;gYU|?=!Y?5eZnVM{3WSnYbVQ6A% zZjfS`mY8N~WNvI?YHV(ioS2$qVUT8MVrZ0{YG9O-l$302o|tNGWN2h&k!oO&W@eO{ zW|3xWU~ZC{Vw#d-Y>{YcmSk#fmXTkSnVy-Km~(>#6vh*6RW`BhU{ycC2nvh&YYJbl zpR}ktsmICq=R?2f9XD@(etwy&=WqY*MHW+o5|&Kngpu#xS z($vYq)yTv$8R$MUH&?@yBqLXoLZ#n16WiFM%~SqtNzV)_Q}#FK^fl}be z7#O;yI2)NKIvJT8TDrNJq?jj~Ihvaro4XiUI2$9pxKdD4?I$xRV7txzXUK~e-PJWH zR*>D0v#ZmMk8#P@tmFw-6H@+h&f@`k&TDeN5D#W54c!g)qjEoUR<*Jt^Gp@}-KsNq z=j<&vWj$WMca9xnkDliRft6@}BxqlWh*A!=cqG}A-yj}b+8VmXs^M(ocQN_5T@P2B z*5z|rePZF!yjW3pnT|)@Za@!ub1_IuPddkZh%vf)@JZp8IL2$KEz?7@OuHJ3upRj+O zcx_sV%^g@`HO-6^qE{Q&V?l+_kbg^ZTg|-F9V$a)i1J^-F7aEOMOL0(bD}o z)A_ppZ1e6pcN5Kn1no8W{>_et{C z6DmgG1t_612Ck4$wuUPN<&AoLnFFqpQ0js!Bos@TpyEBTq_ikCvp6-mz{qs+X(?#{ D>QJm= delta 1219 zcmX?V`OMVFYEf!Yett=D;|;9{RR+dFAq5fN4e1+;U(eW_DNwn;{-MHqFBOBQA7#HD z+c#&n3|IEFjYj^=3W4ew;h}a>?N@xUppNLgui^ z`@~ckbtdPD`E7nI#?5Hh!3Z*buEA|7HqPnyw+nFH`Nk{$!!Ba|#Fq!Ii>pqS`kZIV zcz5dbZX;xEYFT1VX3E#ibJ%vULha+~>{nhHo0)TzcjM*2 z^q9~uF@3C0oAQ;a8@E^c=KkVDvTyP{HkHZMJf@rFc-@#4R&81;za?Izd+zf7(%dbK zPbR!Q5Pgy3?-~2@OOJA!>VKemaq?ran#rdG`wbU>eYvJLYKHHDxXz2Fu95Q$Cmf&s zqju?Glg~_@29F|APMB|9g7D>J1zr_BRBsZrdYzEG+D~Rsa28wn8#UgoEM4xmjQ_R$ zC#k0frE6EU^*`4Ay)e#wv)rD^iNfd+5mgP+c;Z_})-8?6SBiH|URml^ZFBF6bYV`* zDe1KzF1zbAzAAyZ@EfYb2-?;ysswcyzxOo@Pe(lLL% zWB7?y7e-+4d2=yHOHVq-e26i+dh%W|HKfD>aih?woqHdtzhpC8+Ie?&@J#dwTq7bf-P>^O8XQt;R UmXsEyW)`O=7Z{mNUL_+909<(@V*mgE diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 072fc6b1..21dbdb22 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -6,11 +6,12 @@ import { BytesTape } from '../bytes-tape' import { Decoder } from '../decoder' import * as uint8arrays from 'uint8arrays' import { CanonicalizationKind } from '../canonicalization.js' +import { Eip712, fromOriginal } from '../canons/eip712.js' const factory = new CARFactory() test('eip712-secp256k1.car', async () => { - const carFilepath = new URL('./vectors/eip712-secp256k1.car', import.meta.url) + const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) const carBytes = await readFile(carFilepath) const car = factory.fromBytes(carBytes) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment @@ -22,16 +23,18 @@ test('eip712-secp256k1.car', async () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - if (!entry['data']) continue + if (!entry.original) continue // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const data = car.get(entry.data) + const original = car.get(entry.original) + const recalculatedFromOriginal = fromOriginal(original as Eip712) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const node = car.get(entry.node) + expect(node).toEqual(recalculatedFromOriginal) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const varsig = new Decoder(new BytesTape(node._sig)).read() if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const input = varsig.canonicalization(data.message) + const input = varsig.canonicalization(original.message) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const signer = entry.signer // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts index 40f8c396..b107c010 100644 --- a/packages/varsig/src/__tests__/gen-vectors.ts +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -190,7 +190,7 @@ async function main() { { isRoot: true } ) - const carFilepath = new URL('./vectors/eip712-secp256k1.car', import.meta.url) + const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) await pipeline(car, fs.createWriteStream(carFilepath)) } diff --git a/packages/varsig/src/__tests__/parse-eip712.test.ts b/packages/varsig/src/__tests__/parse-eip712.test.ts index 6381a25c..e451bc6a 100644 --- a/packages/varsig/src/__tests__/parse-eip712.test.ts +++ b/packages/varsig/src/__tests__/parse-eip712.test.ts @@ -1,12 +1,9 @@ import { test } from '@jest/globals' -import * as varintes from 'varintes' -import * as uint8arrays from 'uint8arrays' import { privateKeyToAccount } from 'viem/accounts' import { BytesTape } from '../bytes-tape.js' import { CanonicalizationKind } from '../canonicalization.js' import { fromOriginal } from '../canons/eip712' import { Decoder } from '../decoder.js' -import { hex } from './hex.util.js' const testData = { types: { @@ -94,7 +91,7 @@ test('712 flow', async () => { domain: testData.domain, primaryType: testData.primaryType, message: testData.message, - signature: stringSignature + signature: stringSignature, }) const varsig = node._sig const decoder = new Decoder(new BytesTape(varsig)).read() diff --git a/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car b/packages/varsig/src/__tests__/vectors/eip712-secp256k1.car deleted file mode 100644 index 87c4b882bb7dfab30f59ff98abcc08b0a370e06b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7365 zcmeHM3vgA%8RiNiDh~{ z!?benXv=pd9X*{_OiW8RLY{3c?e61St;jxgEPG$t!~skHe(sAl$+>N>ZmBJP=3w)) zdlxM)xKK?ZrD0;NQimcvIJ;5}p+cf+rctQc_1Z9M%m}&FO2lYdnU9T7goGlN+hj#d zJ(TD6pM)Y2g!2n&tJ6r7(g?AlCTB&ba^`cUYB*Gzf`UQf4$DKTY7)x|2C|2V0Q!wW zq=#ZFz4P$c9yDE39?qwuvsI@huSgF?ATPK}nM^lM*GlQ~ee{t`gm0f-Q=N&coE@4J zh~yJ9#on`+MNe!}1e#hZNoPuY*yqDE`Dk)7`yc(-ne)|dq@}z<`miXERMGu*SBAZY z65SEokm6OmG%0qj(A@d6OZHd0VAs&xImW03pE0F@1)m=xR+Y14Rtf;t=a)G#fEhkh z@rhXhk>&iF#8ksPrv z=Tn+MH)4#ZHCN~9(xu|P=G#5FFQmG(OYNVVn`$FGH6ztZ9hZ@t>e7N1QR=vH8L2k@ zoK7}jX>D|v-7{j`UXIZ;ZmOP~v`V4orbq43!S(2-Z6GxRWTbw3{8Ce`9~~iox_{rE z$EQ}#eBx62))Th|k%G7DTFmkf>K^U@&JA3&?pEoTKYrf!)=u_d`)empcKN}eo_%L= zox;b!jqM!^?>%yJy8b?%)^KBf5&iq^=rLuMZ%oxg_R(8Mp6xbb#S!TnqeJg*vwu{5 zVCUG$KMxM=H@3~T&D}mbT}NGkp^~^`PJ|RvjDos)S+j9Z%$0h$ND1nA2q|^&&&ezl zunYtl5J_Pal@~-&g1ib@9w{=%N*rbwkwO; z-~hygfvTclRzyUE016Ue7)^jglQfnQIim8a%1VfmiWh}^!_@Qi5DLa0D3NqrbOdJG zfT|0l&j;pf)yvxKTz^hn`7b$b_+PKA9h|=6()hIZa-ssWQp=1`?;n}#I;5DhbUn1~ zhe6a0YZlHYg~)Z#j$*LT9iy8B>tQ`6Cox@c8`X$Th075V+nT_+)ge+6)=d1R)oISjH?C{5;AMSU*D)z)R3acu9;RNMC0^=3zgq&%_9c_-C;t?E z?7Lu;i5{hG3iC3g-W%{70|3Vgl8hDVtyzW>kPPXQiaeHORTc?{Aty1^#k0IZ-9M*5 zl^`*O7f(G1Y$*hyz%eT0mwYm-WQl-LgaF|nBgwMp^@>pR@?Md;`1(`6+z1iP>UC%p zGVP@bye3lW(bdqzNJXAiR-OMM(zCXKb`yDl>PI&zlYy8_Dw|t%#bN=?4>WYdK6P5F}sJNhX z^dkC>NWFUz5FBN3S!YWD3!REM*=QYA8(UH+f$R|i=M-;eR)dB(qiCHGwPiDj7GuV3 z0@+1ZcA^Wjh39te&@MJvq5|}7*+GqxD^unzJ-&bD2h!QL9fm!UT(DbZR#lgGP3u=P zZeySHD>G7uOM7lEd}_{tV{_l`y!6zJ4mUe`&CNw`27Z^`QCZU6;Z;sbv;UktO<0p~ zNV6y>a#D6y5H!-$QJA;;mepcXMaFM_y7=(Vp6&T!*q7VB#rhMg?;Yab_1G9%zIO82 znu^L7lyWb7?c{U0Zyi4RW!?VA%D-N4v{L%@h>4BVEfM4zsawoB+azU)%zLE(3r?!5rwDP-hV%2g<~fjaO%! zy}YQVqIf{(CCeU8vdqBX4G&k}9^xcI3L&Oxn3Jvukr#?U&dTCc5eb>1$cla_`Pio7kjU-3@w?Uk$8hHH(xTPRz1r^Vz>CSJ zn4)#Bmo50D?~Rq?2W{xS_3nZD+=KZ2eu>|=IN;9u-t`sLS;dRYes8GD4zi`&-|Y3x zrqkV)Pn-T(?viVdBsT(gBBZ$Bj*JRfMd7evMu?hHKuS~Wkn?Wh`zsf>?S?GtbY;

*J-b>WRS%4;^fb71cBn#|Gf&hdNh9&YQJJ-BzePwt&u+|K{uh0ej_mD2O; z@~4em-FME|^LU7Vd2;XTpA!FPq;*+f#*}`IdOc kM2A^_ZKhiUhv|kCfG|cOBcx+`&pp+-`_zaYra#&J8w6dR1^@s6 diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 724b800a..44f3baf9 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -5,6 +5,7 @@ import * as uint8arrays from 'uint8arrays' import { hashTypedData, Hex, TypedDataDomain } from 'viem' import type { CanonicalizationAlgo } from '../canonicalization.js' import type { SigningKind } from '../signing.js' +import stringify from 'fast-json-stable-stringify' interface Eip712Domain { name: string @@ -29,7 +30,7 @@ interface SignatureComponents { v: number } -interface Eip712 { +export interface Eip712 { types: Eip712Types domain: Eip712Domain primaryType: string @@ -239,7 +240,7 @@ export function fromOriginal({ message, signature, }: Eip712): IpldNodeSigned { - const metadata = JSON.stringify([compressTypes(types), primaryType, compressDomain(domain)]) + const metadata = stringify([compressTypes(types), primaryType, compressDomain(domain)]) const metadataBytes = uint8arrays.fromString(metadata) const metadataLength = varintes.encode(metadataBytes.length)[0] if (!signature) throw new Error(`No signature passed`) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc4035f6..87357e56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -585,6 +585,9 @@ importers: codeco: specifier: ^1.1.0 version: 1.1.0 + fast-json-stable-stringify: + specifier: ^2.1.0 + version: 2.1.0 key-did-provider-ed25519: specifier: workspace:^ version: link:../key-did-provider-ed25519 @@ -610,6 +613,9 @@ importers: cartonne: specifier: ^2.2.0 version: 2.2.0 + tsm: + specifier: ^2.3.0 + version: 2.3.0 website: dependencies: From 626cf7dd20d4a1d9a7e352c3aa7d451b5efcf425 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 15:27:44 +0300 Subject: [PATCH 086/110] recoverybit --- packages/varsig/src/__tests__/canons/eip712.test.ts | 4 ++-- packages/varsig/src/canonicalization.ts | 2 +- packages/varsig/src/canons/eip712.ts | 4 ++-- packages/varsig/src/signing.ts | 7 +++---- packages/varsig/src/signing/secp256k1.ts | 1 + packages/varsig/src/varsig.ts | 7 +++++-- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/varsig/src/__tests__/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts index b36c89df..edbca0ee 100644 --- a/packages/varsig/src/__tests__/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -50,7 +50,7 @@ const expectedHash = uint8arrays.fromString( 'base16' ) -test('Encode eip712 message', async () => { +test('Encode eip712 message', () => { // @ts-ignore const node = fromOriginal(testData) @@ -58,7 +58,7 @@ test('Encode eip712 message', async () => { expect(node.attachment instanceof Uint8Array).toBeTruthy() }) -test('Canonicalize ipld eip712 object', async () => { +test('Canonicalize ipld eip712 object', () => { // @ts-ignore const node = fromOriginal(testData) const tape = new BytesTape(node._sig) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index d7f8b760..276b14ce 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -21,7 +21,7 @@ type CanonicalizationEIP191 = { type CanonicalizationEIP712 = { kind: CanonicalizationKind.EIP712 (message: any): Uint8Array - original(node: IpldNode, signature: Uint8Array, recoveryBit: number): any + original(node: IpldNode, signature: Uint8Array, recoveryBit: number | undefined): any } export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP712 diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 44f3baf9..4455896e 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -185,10 +185,10 @@ export function prepareCanonicalization( const hexHash = hashTypedData({ ...metadata, message }) return uint8arrays.fromString(hexHash.slice(2), 'base16') } - const original = (node: IpldNode, signature: Uint8Array, recoveryBit: number) => { + const original = (node: IpldNode, signature: Uint8Array, recoveryBit: number | undefined) => { const message = ipldNodeToMessage(node) - const sigBytes = uint8arrays.concat([signature, [recoveryBit]]) + const sigBytes = recoveryBit ? uint8arrays.concat([signature, [recoveryBit]]) : signature const sigHex = `0x${uint8arrays.toString(sigBytes, 'base16')}` return { ...metadata, message, signature: sigHex } } diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 75e24ee5..4d110556 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -13,6 +13,7 @@ export enum SigningKind { export type SigningAlgo = { kind: SigningKind + recoveryBit?: number verify: VerifySignatureFn } @@ -32,9 +33,8 @@ export class SigningDecoder { read(): SigningAlgo { const signingSigil = this.tape.readVarint() switch (signingSigil) { - case SigningKind.SECP256K1: { + case SigningKind.SECP256K1: return Secp256k1.prepareVerifier(this.tape) - } case SigningKind.RSA: throw new Error(`Not implemented: signingSigil: RSA`) default: @@ -44,9 +44,8 @@ export class SigningDecoder { readSignature(signing: SigningAlgo): Uint8Array { switch (signing.kind) { - case SigningKind.SECP256K1: { + case SigningKind.SECP256K1: return Secp256k1.readSignature(this.tape) - } case SigningKind.RSA: { throw new Error(`Not supported: RSA`) } diff --git a/packages/varsig/src/signing/secp256k1.ts b/packages/varsig/src/signing/secp256k1.ts index a5ffa56f..27507688 100644 --- a/packages/varsig/src/signing/secp256k1.ts +++ b/packages/varsig/src/signing/secp256k1.ts @@ -13,6 +13,7 @@ function prepareVerifier(tape: BytesTape): SigningAlgo { } return { kind: SIGIL, + recoveryBit: recoveryBit, // eslint-disable-next-line @typescript-eslint/require-await verify: async (input, signature, verificationKey): Promise => { let k1Sig = secp256k1.Signature.fromCompact(signature) diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 314657a0..15d7d273 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -1,4 +1,5 @@ import { Decoder } from './decoder' +import { CanonicalizationKind } from './canonicalization' export { Eip712 } from './canons/eip712' @@ -35,6 +36,8 @@ export async function toOriginal(node: VarsigNode): Promise { const { canonicalization, signing, signature } = new Decoder(node._sig).read() // @ts-ignore delete node._sig - // @ts-ignore - return canonicalization.original(node, signature, signing.recoveryBig) + if (canonicalization.kind !== CanonicalizationKind.EIP712) + throw new Error(`Supported just for EIP712`) + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return canonicalization.original(node, signature, signing.recoveryBit) } From 8fd01d6cbeb0a55198c97161130069776fb9f836 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 15:45:25 +0300 Subject: [PATCH 087/110] JWS canon --- packages/varsig/src/canons/eip712.ts | 2 +- packages/varsig/src/canons/jws.ts | 138 ++++++++++++++++++--------- pnpm-lock.yaml | 22 +++-- 3 files changed, 111 insertions(+), 51 deletions(-) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 4455896e..0c661c8b 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -252,7 +252,7 @@ export function fromOriginal({ varintes.encode(0xe7)[0], // key type recoveryBit, varintes.encode(0x1b)[0], // hash type - varintes.encode(0xe712)[0], // canonicalizer codec + varintes.encode(SIGIL)[0], // canonicalizer codec metadataLength, metadataBytes, signatureBytes, diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index 184f1733..0bcaaa67 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -1,43 +1,95 @@ -// import type { GeneralJWS } from 'dids' -// import * as uint8arrays from 'uint8arrays' -// import { toBytes } from '../bytes.js' -// import { ENCODING, HASHING, SIGNING } from '../varsig.js' -// -// export function encode(jws: GeneralJWS) { -// const signature0 = jws.signatures[0] -// // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -// const protectedHeader = JSON.parse( -// uint8arrays.toString(uint8arrays.fromString(signature0.protected, 'base64url')) -// ) -// const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(jws.payload, 'base64url'))) -// const signature = uint8arrays.fromString(signature0.signature, 'base64url') -// // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -// switch (protectedHeader.alg) { -// case 'EdDSA': { -// const sig = toBytes({ -// encoding: ENCODING.JWT, -// hashing: HASHING.SHA2_256, -// signing: SIGNING.ED25519, -// signature: signature, -// }) -// delete protectedHeader.typ -// delete protectedHeader.alg -// return { -// _header: protectedHeader, -// ...payload, -// _signature: sig, -// } -// } -// case 'ES256': { -// const sig = toBytes({ -// encoding: ENCODING.JWT, -// hashing: HASHING.SHA2_256, -// signing: SIGNING.RSA -// }) -// } -// } -// return { -// _header: {}, -// _sig: {}, -// } -// } +import { CanonicalizationAlgo } from '../canonicalization.js' +import { BytesTape } from '../bytes-tape.js' +import { HashingAlgo } from '../hashing' +import { SigningKind } from '../signing' +import * as varintes from 'varintes' +import * as uint8arrays from 'uint8arrays' +import { encode, decode } from '@ipld/dag-json' + +type IpldNode = Record +type IpldNodeSigned = IpldNode & { _sig: Uint8Array } + +const KEY_TYPE_BY_ALG_CRV: Record> = { + ES256: { default: 0x1200 }, + EdDSA: { ed448: 0x1203, ed25519: 0xec, default: 0xec }, + ES256K: { default: 0xe7 }, +} +const HASH_BY_KEY_TYPE: Record = { + 0x1200: 0x12, + 0xe7: 0x12, + 0xec: 0x12, + 0x1203: 0x19, +} + +const toB64u = (bytes: Uint8Array) => uint8arrays.toString(bytes, 'base64url') +const fromB64u = (b64u: string) => uint8arrays.fromString(b64u, 'base64url') + + +const SIGIL = 0x7053 // jose + +export const JWS = { SIGIL, prepareCanonicalization, fromOriginal } + +export function prepareCanonicalization( + tape: BytesTape, + hashType: HashingAlgo, + keyType: SigningKind +): CanonicalizationAlgo { + const protectedLength = tape.readVarint() + const protectedBytes = tape.read(protectedLength) + const protected1 = JSON.parse(uint8arrays.toString(protectedBytes)) + + const keyTypeFromProtected = findKeyType(protected1) + if (keyType !== keyTypeFromProtected) throw new Error(`Key type missmatch: ${keyType}, ${keyTypeFromProtected}`) + if (hashType !== HASH_BY_KEY_TYPE[keyType]) throw new Error(`Hash type missmatch: ${hashType}, ${HASH_BY_KEY_TYPE[keyType]}`) + + const can = (node: IpldNode) => { + // encode node using dag-json from multiformats + const payloadB64u = toB64u(uint8arrays.fromString(JSON.stringify(encode(node)))) + const protectedB64u = toB64u(protectedBytes) + return uint8arrays.fromString(`${protectedB64u}.${payloadB64u}`) + } + can.kind = SIGIL + can.original = (node: IpldNode, signature: Uint8Array) => { + const payloadB64u = toB64u(encode(node)) + const protectedB64u = toB64u(protectedBytes) + const signatureB64u = toB64u(signature) + return `${protectedB64u}.${payloadB64u}.${signatureB64u}` + } + return can +} + +export function fromOriginal(jws: string): IpldNodeSigned { + const [protectedB64u, payloadB64u, signatureB64u] = jws.split('.') + const node = decode(fromB64u(payloadB64u)) as IpldNode + const protectedBytes = fromB64u(protectedB64u) + const protected1 = JSON.parse(uint8arrays.toString(protectedBytes)) + const protectedLength = varintes.encode(protectedBytes.length)[0] + const signature = fromB64u(signatureB64u) + + const keyType = findKeyType(protected1) + const hashType = HASH_BY_KEY_TYPE[keyType] + + // TODO - this doesn't currently support RSA signatures + const varsig = uint8arrays.concat([ + new Uint8Array([0x34]), // varsig sigil + varintes.encode(keyType)[0], // key type + varintes.encode(hashType)[0], // hash type + varintes.encode(SIGIL)[0], // canonicalizer codec + protectedLength, + protectedBytes, + signature, + ]) + return { ...node, _sig: varsig } +} + +interface ProtectedHeader { + alg: string + crv?: string +} + +function findKeyType({ alg, crv }: ProtectedHeader): number { + if (!alg) throw new Error(`Missing alg in protected header`) + const keyType = KEY_TYPE_BY_ALG_CRV[alg][crv || 'default'] + if (!keyType) throw new Error(`Unsupported alg: ${alg}, or crv: ${crv}`) + return keyType +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87357e56..c483ddd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -573,6 +573,9 @@ importers: packages/varsig: dependencies: + '@ipld/dag-json': + specifier: ^10.1.5 + version: 10.1.5 '@noble/curves': specifier: ^1.2.0 version: 1.2.0 @@ -4471,12 +4474,12 @@ packages: cborg: 1.10.2 multiformats: 11.0.2 - /@ipld/dag-json@10.1.0: - resolution: {integrity: sha512-2rSvzDyGxx1NC24IsqKFTSXzAfUBlniZQRT15PEN+i177KEBsCXPfxuN/DweGIfmj3YceNyR8XOJT47pRZu7Cg==} + /@ipld/dag-json@10.1.5: + resolution: {integrity: sha512-AIIDRGPgIqVG2K1O42dPDzNOfP0YWV/suGApzpF+YWZLwkwdGVsxjmXcJ/+rwOhRGdjpuq/xQBKPCu1Ao6rdOQ==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: - cborg: 1.10.2 - multiformats: 11.0.2 + cborg: 4.0.3 + multiformats: 12.1.3 dev: false /@ipld/dag-pb@4.0.3: @@ -12385,7 +12388,7 @@ packages: '@chainsafe/libp2p-noise': 11.0.4 '@ipld/car': 5.1.1 '@ipld/dag-cbor': 9.0.1 - '@ipld/dag-json': 10.1.0 + '@ipld/dag-json': 10.1.5 '@ipld/dag-pb': 4.0.3 '@libp2p/bootstrap': 6.0.3 '@libp2p/crypto': 1.0.17 @@ -12467,7 +12470,7 @@ packages: deprecated: js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details dependencies: '@ipld/dag-cbor': 9.0.1 - '@ipld/dag-json': 10.1.0 + '@ipld/dag-json': 10.1.5 '@ipld/dag-pb': 4.0.3 '@libp2p/logger': 2.1.1 '@libp2p/peer-id': 2.0.3 @@ -14860,6 +14863,11 @@ packages: resolution: {integrity: sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} + /multiformats@12.1.3: + resolution: {integrity: sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==} + engines: {node: '>=16.0.0', npm: '>=7.0.0'} + dev: false + /multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} From 002bb523f08dfb0b2549428eadc0067aba8f0f17 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 16:11:33 +0300 Subject: [PATCH 088/110] Add magic --- packages/varsig/package.json | 1 + packages/varsig/src/bytes.ts | 3 ++- packages/varsig/src/canonicalization.ts | 4 ++-- packages/varsig/src/canons/eip712.ts | 15 ++++++++------- packages/varsig/src/canons/jws.ts | 19 ++++++++++--------- packages/varsig/src/decoder.ts | 3 ++- packages/varsig/src/hashing.ts | 7 ++++--- packages/varsig/src/magic.ts | 16 ++++++++++++++++ packages/varsig/src/signing.ts | 5 +++-- packages/varsig/src/signing/secp256k1.ts | 3 ++- 10 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 packages/varsig/src/magic.ts diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 9f0a240d..2c7761b7 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -51,6 +51,7 @@ "tsm": "^2.3.0" }, "dependencies": { + "@ipld/dag-json": "^10.1.5", "@noble/curves": "^1.2.0", "@noble/hashes": "^1.3.2", "@types/node": "^20.2.3", diff --git a/packages/varsig/src/bytes.ts b/packages/varsig/src/bytes.ts index 483c3195..f15eba2c 100644 --- a/packages/varsig/src/bytes.ts +++ b/packages/varsig/src/bytes.ts @@ -1,4 +1,5 @@ import * as varintes from 'varintes' +import { MAGIC } from './magic.js' type VarsigBytes = { encoding: number @@ -7,7 +8,7 @@ type VarsigBytes = { signature: Uint8Array } -const VARSIG_SIGIL = 0x34 +const VARSIG_SIGIL = MAGIC.VARSIG const VARSIG_SIGIL_BYTES = new Uint8Array([VARSIG_SIGIL]) export function fromBytes(bytes: Uint8Array): VarsigBytes { diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 276b14ce..21dab4b5 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -7,8 +7,8 @@ import { keccak_256 } from '@noble/hashes/sha3' import type { SigningKind } from './signing.js' export enum CanonicalizationKind { - EIP712 = Eip712.SIGIL, - EIP191 = 0xe191, + EIP712 = MAGIC.EIP712, + EIP191 = MAGIC.EIP191 } type IpldNode = Record diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 0c661c8b..e826362c 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -3,6 +3,7 @@ import type { HashingAlgo } from '../hashing.js' import * as varintes from 'varintes' import * as uint8arrays from 'uint8arrays' import { hashTypedData, Hex, TypedDataDomain } from 'viem' +import { MAGIC } from '../magic.js' import type { CanonicalizationAlgo } from '../canonicalization.js' import type { SigningKind } from '../signing.js' import stringify from 'fast-json-stable-stringify' @@ -153,12 +154,12 @@ const SOLIDITY_TO_COMPRESSED = Object.fromEntries( ) const SUPPORTED_KEY_TYPES = [ - 0xe7, // secp256k1 - // 0x1271, // eip1271 contract signature + MAGIC.SECP256K1, + // MAGIC.EIP1271, ] -const SUPPORTED_HASH_TYPE = 0x1b // keccak256 +const SUPPORTED_HASH_TYPE = MAGIC.KECCAK_256 -const SIGIL = 0xe712 +const SIGIL = MAGIC.EIP712 export function prepareCanonicalization( tape: BytesTape, @@ -248,10 +249,10 @@ export function fromOriginal({ const recoveryBit = extracted.recoveryBit const signatureBytes = extracted.bytes const varsig = uint8arrays.concat([ - new Uint8Array([0x34]), // varsig sigil - varintes.encode(0xe7)[0], // key type + new Uint8Array([MAGIC.VARSIG]), // varsig sigil + varintes.encode(MAGIC.SECP256K1)[0], // key type recoveryBit, - varintes.encode(0x1b)[0], // hash type + varintes.encode(MAGIC.KECCAK_256)[0], // hash type varintes.encode(SIGIL)[0], // canonicalizer codec metadataLength, metadataBytes, diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index 0bcaaa67..9b5cdf32 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -1,3 +1,4 @@ +import { MAGIC } from '../magic.js' import { CanonicalizationAlgo } from '../canonicalization.js' import { BytesTape } from '../bytes-tape.js' import { HashingAlgo } from '../hashing' @@ -10,22 +11,22 @@ type IpldNode = Record type IpldNodeSigned = IpldNode & { _sig: Uint8Array } const KEY_TYPE_BY_ALG_CRV: Record> = { - ES256: { default: 0x1200 }, - EdDSA: { ed448: 0x1203, ed25519: 0xec, default: 0xec }, - ES256K: { default: 0xe7 }, + ES256: { default: MAGIC.ES256 }, + EdDSA: { ed448: MAGIC.ED448, ed25519: MAGIC.ED25519, default: MAGIC.ED25519 }, + ES256K: { default: MAGIC.SECP256K1 }, } const HASH_BY_KEY_TYPE: Record = { - 0x1200: 0x12, - 0xe7: 0x12, - 0xec: 0x12, - 0x1203: 0x19, + [MAGIC.ES256]: MAGIC.SHA2_256, + [MAGIC.SECP256K1]: MAGIC.SHA2_256, + [MAGIC.ED25519]: MAGIC.SHA2_256, + [MAGIC.ED448]: MAGIC.SHAKE_256, } const toB64u = (bytes: Uint8Array) => uint8arrays.toString(bytes, 'base64url') const fromB64u = (b64u: string) => uint8arrays.fromString(b64u, 'base64url') -const SIGIL = 0x7053 // jose +const SIGIL = MAGIC.JOSE // jose export const JWS = { SIGIL, prepareCanonicalization, fromOriginal } @@ -71,7 +72,7 @@ export function fromOriginal(jws: string): IpldNodeSigned { // TODO - this doesn't currently support RSA signatures const varsig = uint8arrays.concat([ - new Uint8Array([0x34]), // varsig sigil + new Uint8Array([MAGIC.VARSIG]), // varsig sigil varintes.encode(keyType)[0], // key type varintes.encode(hashType)[0], // hash type varintes.encode(SIGIL)[0], // canonicalizer codec diff --git a/packages/varsig/src/decoder.ts b/packages/varsig/src/decoder.ts index a4da8065..3db20c0d 100644 --- a/packages/varsig/src/decoder.ts +++ b/packages/varsig/src/decoder.ts @@ -1,3 +1,4 @@ +import { MAGIC } from './magic.js' import type { BytesTape } from './bytes-tape.js' import { SigningDecoder, type SigningAlgo } from './signing.js' import { HashingDecoder, type HashingAlgo } from './hashing.js' @@ -34,7 +35,7 @@ export class Decoder { readVarsigSigil() { const sigil = this.#tape.readVarint() - if (sigil !== 0x34) throw new Error(`Not a varsig`) + if (sigil !== MAGIC.VARSIG) throw new Error(`Not a varsig`) return sigil } } diff --git a/packages/varsig/src/hashing.ts b/packages/varsig/src/hashing.ts index a79c775e..d11706e8 100644 --- a/packages/varsig/src/hashing.ts +++ b/packages/varsig/src/hashing.ts @@ -1,10 +1,11 @@ +import { MAGIC } from './magic.js' import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' export enum HashingAlgo { - SHA2_256 = 0x12, - SHA2_512 = 0x13, - KECCAK256 = 0x1b, + SHA2_256 = MAGIC.SHA2_256, + SHA2_512 = MAGIC.SHA2_512, + KECCAK256 = MAGIC.KECCAK_256, } export class HashingDecoder { diff --git a/packages/varsig/src/magic.ts b/packages/varsig/src/magic.ts new file mode 100644 index 00000000..68187969 --- /dev/null +++ b/packages/varsig/src/magic.ts @@ -0,0 +1,16 @@ +export const MAGIC = { + SHA2_256: 0x12, // sha2-256 + SHA2_512: 0x13, // sha2-512 + SHAKE_256: 0x19, // shake-256 + KECCAK_256: 0x1b, // keccak-256 + VARSIG: 0x34, // varsig + ED25519: 0xec, // ed25519 + SECP256K1: 0xe7, // secp256k1 + ES256: 0x1200, // ES256 + ED448: 0x1203, // ED448 + RSA: 0x1205, // RSA + EIP1271: 0x1271, // EIP1271 + JOSE: 0x7053, // JOSE + EIP191: 0xe191, // ethereum signed message + EIP712: 0xe712, // contract signature +} diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 4d110556..c17fb089 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -1,3 +1,4 @@ +import { MAGIC } from './magic.js' import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' import { Secp256k1 } from './signing/secp256k1.js' @@ -7,8 +8,8 @@ type PublicKey = Uint8Array type VerificationKey = PublicKey | EthAddress export enum SigningKind { - RSA = 0x1205, - SECP256K1 = Secp256k1.SIGIL, + RSA = MAGIC.RSA, + SECP256K1 = MAGIC.SECP256K1, } export type SigningAlgo = { diff --git a/packages/varsig/src/signing/secp256k1.ts b/packages/varsig/src/signing/secp256k1.ts index 27507688..4a213bbf 100644 --- a/packages/varsig/src/signing/secp256k1.ts +++ b/packages/varsig/src/signing/secp256k1.ts @@ -1,10 +1,11 @@ +import { MAGIC } from '../magic.js' import { SigningAlgo } from '../signing.js' import { secp256k1 } from '@noble/curves/secp256k1' import * as uint8arrays from 'uint8arrays' import { keccak_256 } from '@noble/hashes/sha3' import type { BytesTape } from '../bytes-tape.js' -const SIGIL = 0xe7 +const SIGIL = MAGIC.SECP256K1 function prepareVerifier(tape: BytesTape): SigningAlgo { const recoveryBit = tape.read(1)[0] From ec55e18d09a52b418945fac6574fbdbdd9211cd8 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 16:13:26 +0300 Subject: [PATCH 089/110] Fix magic --- packages/varsig/src/canonicalization.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 21dab4b5..edc3ac2f 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -1,3 +1,4 @@ +import { MAGIC } from './magic.js' import { BytesTape } from './bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { UnreacheableCaseError } from './unreachable-case-error.js' From 3c45f197d822f6f05d208622fdbde74a061370d6 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 16:15:48 +0300 Subject: [PATCH 090/110] tooriginal --- packages/varsig/package.json | 1 + .../varsig/src/__tests__/eip712-secp256k1.test.ts | 13 ++++++++++++- packages/varsig/src/canons/eip712.ts | 2 +- packages/varsig/src/varsig.ts | 15 +++++++++------ pnpm-lock.yaml | 8 ++++++++ 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/varsig/package.json b/packages/varsig/package.json index 2c7761b7..edf39554 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -59,6 +59,7 @@ "fast-json-stable-stringify": "^2.1.0", "key-did-provider-ed25519": "workspace:^", "key-did-resolver": "workspace:^", + "klona": "^2.0.6", "multiformats": "^11.0.2", "uint8arrays": "^4.0.3", "varintes": "^2.0.5", diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 21dbdb22..2902cbe9 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -6,7 +6,9 @@ import { BytesTape } from '../bytes-tape' import { Decoder } from '../decoder' import * as uint8arrays from 'uint8arrays' import { CanonicalizationKind } from '../canonicalization.js' -import { Eip712, fromOriginal } from '../canons/eip712.js' +import { Eip712, EIP712_DOMAIN, fromOriginal } from '../canons/eip712.js' +import { toOriginal } from '../varsig.js' +import { klona } from 'klona' const factory = new CARFactory() @@ -30,6 +32,15 @@ test('eip712-secp256k1.car', async () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const node = car.get(entry.node) expect(node).toEqual(recalculatedFromOriginal) + let originalKlone = klona(original) + if (Object.keys(original.signature).includes('r')) { + const r = uint8arrays.fromString(original.signature.r.replace(/^0x/, ''), 'hex') + const s = uint8arrays.fromString(original.signature.s.replace(/^0x/, ''), 'hex') + originalKlone.signature = + '0x' + uint8arrays.toString(uint8arrays.concat([r, s, [original.signature.v]]), 'hex') + } + originalKlone.types['EIP712Domain'] = EIP712_DOMAIN + await expect(toOriginal(node)).resolves.toEqual(originalKlone) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const varsig = new Decoder(new BytesTape(node._sig)).read() if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index e826362c..3d887b27 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -291,7 +291,7 @@ function messageToIpld( return node as IpldNode } -const EIP712_DOMAIN = [ +export const EIP712_DOMAIN = [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 15d7d273..7988dbdc 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -1,5 +1,7 @@ -import { Decoder } from './decoder' -import { CanonicalizationKind } from './canonicalization' +import { Decoder } from './decoder.js' +import { CanonicalizationKind } from './canonicalization.js' +import { BytesTape } from './bytes-tape.js' +import { klona } from 'klona' export { Eip712 } from './canons/eip712' @@ -32,12 +34,13 @@ export async function verify( // eslint-disable-next-line @typescript-eslint/require-await export async function toOriginal(node: VarsigNode): Promise { + if (!node._sig || node._sig.length === 0) throw new Error(`No signature passed`) + const { canonicalization, signing, signature } = new Decoder(new BytesTape(node._sig)).read() + const clone = klona(node) // @ts-ignore - const { canonicalization, signing, signature } = new Decoder(node._sig).read() - // @ts-ignore - delete node._sig + delete clone._sig if (canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Supported just for EIP712`) // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return canonicalization.original(node, signature, signing.recoveryBit) + return canonicalization.original(clone, signature, signing.recoveryBit) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c483ddd3..1dd3fc44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -597,6 +597,9 @@ importers: key-did-resolver: specifier: workspace:^ version: link:../key-did-resolver + klona: + specifier: ^2.0.6 + version: 2.0.6 multiformats: specifier: ^11.0.2 version: 11.0.2 @@ -13988,6 +13991,11 @@ packages: engines: {node: '>=6'} dev: true + /klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + dev: false + /knex@2.4.2(pg@8.11.0)(sqlite3@5.1.6): resolution: {integrity: sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==} engines: {node: '>=12'} From fecde32a0c3228cf7713a4d4ca7f764132193fe2 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 16:18:26 +0300 Subject: [PATCH 091/110] fix --- packages/varsig/src/canonicalization.ts | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index edc3ac2f..c391c008 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -9,7 +9,7 @@ import type { SigningKind } from './signing.js' export enum CanonicalizationKind { EIP712 = MAGIC.EIP712, - EIP191 = MAGIC.EIP191 + EIP191 = MAGIC.EIP191, } type IpldNode = Record diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1dd3fc44..12344ace 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true From cb642f5a7b6dcbda0cbd02f04248f1fd523ca84b Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 16:29:53 +0300 Subject: [PATCH 092/110] change files --- .../__vectors__/eip712-secp256k1.car | Bin 7365 -> 7492 bytes .../src/__tests__/eip712-secp256k1.test.ts | 1 - packages/varsig/src/__tests__/gen-vectors.ts | 6 ++++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/varsig/src/__tests__/__vectors__/eip712-secp256k1.car b/packages/varsig/src/__tests__/__vectors__/eip712-secp256k1.car index e22734c8161cc6f9f99480d144051fd5ce72e6c0..92ebec093b3a20966f77746170dcec9bb92e2f1c 100644 GIT binary patch delta 277 zcmX?VdBn=dYEf!Yett=D;|;9{RR+dFA%&u?cK+X2y=B~gT14@(W-?pR*$_S3-+~=& z>R;rpFZ(OE(I|^e?;|@%ss8m{Kg&A{<_HU_YpnRQM6ojUWA0IQ*TA{2c(%G_J=m}` zwWP8jwRq9wQ*5^1 o9X|YNaurVtL8CXz@%r!)bnE0UX>V0R_JMsqIa)|%vYgCv0PXmGtpET3 delta 273 zcmX?Nb=1tqEO;e9u zy8UJHi6V{UjYe5)dY{-pO840;c_04HwJUAuJl><5-k2}^V5uB<{QDXYtp#7=l+Q&h zO)aS`NG)DC`4rpj%_ { originalKlone.signature = '0x' + uint8arrays.toString(uint8arrays.concat([r, s, [original.signature.v]]), 'hex') } - originalKlone.types['EIP712Domain'] = EIP712_DOMAIN await expect(toOriginal(node)).resolves.toEqual(originalKlone) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const varsig = new Decoder(new BytesTape(node._sig)).read() diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts index b107c010..a2b05be9 100644 --- a/packages/varsig/src/__tests__/gen-vectors.ts +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -76,6 +76,12 @@ const EAS_DATA = { { name: 'refUID', type: 'bytes32' }, { name: 'data', type: 'bytes' }, ], + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], }, signature: { v: 27, From ee57f90fd281cecd174b208b1b9bb4cb9e738297 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 16:39:29 +0300 Subject: [PATCH 093/110] Broke the tests --- .../src/__tests__/eip712-secp256k1.test.ts | 23 ++++++++++++++++++- packages/varsig/src/varsig.ts | 5 ++-- pnpm-lock.yaml | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index f8abb518..828ec1be 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -7,7 +7,7 @@ import { Decoder } from '../decoder' import * as uint8arrays from 'uint8arrays' import { CanonicalizationKind } from '../canonicalization.js' import { Eip712, EIP712_DOMAIN, fromOriginal } from '../canons/eip712.js' -import { toOriginal } from '../varsig.js' +import { verify, toOriginal } from '../varsig.js' import { klona } from 'klona' const factory = new CARFactory() @@ -71,3 +71,24 @@ test('eip712-secp256k1.car', async () => { } } }) + +test('eip712-secp256k1.car verify signature', async () => { + const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) + const carBytes = await readFile(carFilepath) + const car = factory.fromBytes(carBytes) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const root = car.get(car.roots[0]) + if (!root) throw new Error(`Empty root`) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const entries = root.entries as Array + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + const node = car.get(entry.node) + console.log('time', node) + + const verificationKey = entry.signer.address || uint8arrays.fromString(entry.signer.publicKey.slice(2)) + + expect(await verify(node, verificationKey)).toEqual(entry.valid) + } +}) diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 7988dbdc..273cf7e2 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -1,6 +1,6 @@ import { Decoder } from './decoder.js' -import { CanonicalizationKind } from './canonicalization.js' import { BytesTape } from './bytes-tape.js' +import { CanonicalizationKind } from './canonicalization.js' import { klona } from 'klona' export { Eip712 } from './canons/eip712' @@ -19,8 +19,9 @@ export async function verify( node: VarsigNode, verificationKey: PublicKey | EthAddress ): Promise { + const tape = new BytesTape(node._sig) // @ts-ignore - const { canonicalization, signing, signature } = new Decoder(node._sig).read() + const { canonicalization, signing, signature } = new Decoder(tape).read() // @ts-ignore delete node._sig diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12344ace..1dd3fc44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true From a68834b49656881437037a47deef04ced5148b4c Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 16:42:57 +0300 Subject: [PATCH 094/110] does it build? --- packages/varsig/src/__tests__/eip712-secp256k1.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 828ec1be..4e68363e 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -6,7 +6,7 @@ import { BytesTape } from '../bytes-tape' import { Decoder } from '../decoder' import * as uint8arrays from 'uint8arrays' import { CanonicalizationKind } from '../canonicalization.js' -import { Eip712, EIP712_DOMAIN, fromOriginal } from '../canons/eip712.js' +import { Eip712, fromOriginal } from '../canons/eip712.js' import { verify, toOriginal } from '../varsig.js' import { klona } from 'klona' From 49ee4f1ee74254664982450ab0ba8998a8b81ed1 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 18:28:55 +0300 Subject: [PATCH 095/110] cleanup --- .../src/__tests__/eip712-secp256k1.test.ts | 149 +++++++++--------- 1 file changed, 75 insertions(+), 74 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 4e68363e..7a747580 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -12,83 +12,84 @@ import { klona } from 'klona' const factory = new CARFactory() -test('eip712-secp256k1.car', async () => { - const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) - const carBytes = await readFile(carFilepath) - const car = factory.fromBytes(carBytes) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const root = car.get(car.roots[0]) - if (!root) throw new Error(`Empty root`) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const entries = root.entries as Array - for (const entryCID of entries) { +describe('eip712-secp256k1.car', () => { + let car, entries + + beforeAll(async () => { + const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) + const carBytes = await readFile(carFilepath) + car = factory.fromBytes(carBytes) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const entry = car.get(entryCID) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - if (!entry.original) continue - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const original = car.get(entry.original) - const recalculatedFromOriginal = fromOriginal(original as Eip712) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const node = car.get(entry.node) - expect(node).toEqual(recalculatedFromOriginal) - let originalKlone = klona(original) - if (Object.keys(original.signature).includes('r')) { - const r = uint8arrays.fromString(original.signature.r.replace(/^0x/, ''), 'hex') - const s = uint8arrays.fromString(original.signature.s.replace(/^0x/, ''), 'hex') - originalKlone.signature = - '0x' + uint8arrays.toString(uint8arrays.concat([r, s, [original.signature.v]]), 'hex') - } - await expect(toOriginal(node)).resolves.toEqual(originalKlone) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const varsig = new Decoder(new BytesTape(node._sig)).read() - if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const input = varsig.canonicalization(original.message) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const signer = entry.signer - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - if (signer.publicKey) { - const verificationResult = await varsig.signing.verify( - input, - varsig.signature, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - uint8arrays.fromString(signer.publicKey.replace(/^0x/, ''), 'hex') - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,jest/no-conditional-expect - expect(verificationResult).toEqual(entry.valid) - } + const root = car.get(car.roots[0]) + if (!root) throw new Error(`Empty root`) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (signer.address) { - const verificationResult = await varsig.signing.verify( - input, - varsig.signature, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access - signer.address - ) - // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-member-access - expect(verificationResult).toEqual(entry.valid) - } - } -}) + entries = root.entries as Array + }) -test('eip712-secp256k1.car verify signature', async () => { - const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) - const carBytes = await readFile(carFilepath) - const car = factory.fromBytes(carBytes) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const root = car.get(car.roots[0]) - if (!root) throw new Error(`Empty root`) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const entries = root.entries as Array - for (const entryCID of entries) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const entry = car.get(entryCID) - const node = car.get(entry.node) - console.log('time', node) + test('Verify signatures', async () => { + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + const node = car.get(entry.node) + console.log('time', node) + + const verificationKey = + entry.signer.address || uint8arrays.fromString(entry.signer.publicKey.slice(2)) - const verificationKey = entry.signer.address || uint8arrays.fromString(entry.signer.publicKey.slice(2)) + expect(await verify(node, verificationKey)).toEqual(entry.valid) + } + }) - expect(await verify(node, verificationKey)).toEqual(entry.valid) - } + test('everything old', async () => { + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + if (!entry.original) continue + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const original = car.get(entry.original) + const recalculatedFromOriginal = fromOriginal(original as Eip712) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const node = car.get(entry.node) + expect(node).toEqual(recalculatedFromOriginal) + const originalKlone = klona(original) + if (Object.keys(original.signature).includes('r')) { + const r = uint8arrays.fromString(original.signature.r.replace(/^0x/, ''), 'hex') + const s = uint8arrays.fromString(original.signature.s.replace(/^0x/, ''), 'hex') + originalKlone.signature = + '0x' + uint8arrays.toString(uint8arrays.concat([r, s, [original.signature.v]]), 'hex') + } + await expect(toOriginal(node)).resolves.toEqual(originalKlone) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const varsig = new Decoder(new BytesTape(node._sig)).read() + if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const input = varsig.canonicalization(original.message) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const signer = entry.signer + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + if (signer.publicKey) { + const verificationResult = await varsig.signing.verify( + input, + varsig.signature, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + uint8arrays.fromString(signer.publicKey.replace(/^0x/, ''), 'hex') + ) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,jest/no-conditional-expect + expect(verificationResult).toEqual(entry.valid) + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (signer.address) { + const verificationResult = await varsig.signing.verify( + input, + varsig.signature, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access + signer.address + ) + // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-member-access + expect(verificationResult).toEqual(entry.valid) + } + } + }) }) + From 5e8295195114c9713b1697b34fa8b0edd5005cb7 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 18:34:18 +0300 Subject: [PATCH 096/110] half test --- .../src/__tests__/eip712-secp256k1.test.ts | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 7a747580..44c6b3d5 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -6,7 +6,7 @@ import { BytesTape } from '../bytes-tape' import { Decoder } from '../decoder' import * as uint8arrays from 'uint8arrays' import { CanonicalizationKind } from '../canonicalization.js' -import { Eip712, fromOriginal } from '../canons/eip712.js' +import { Eip712 } from '../canons/eip712.js' import { verify, toOriginal } from '../varsig.js' import { klona } from 'klona' @@ -40,7 +40,36 @@ describe('eip712-secp256k1.car', () => { } }) - test('everything old', async () => { + test('Create varsig ipld node', async () => { + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + if (!entry.original) continue + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const original = car.get(entry.original) + const varsigNode = Eip712.fromOriginal(original as Eip712) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const node = car.get(entry.node) + expect(varsigNode).toEqual(node) + } + }) + + test('Recover original from ipld node', async () => { + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + if (!entry.original) continue + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const original = car.get(entry.original) + const varsigNode = Eip712.fromOriginal(original as Eip712) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const node = car.get(entry.node) + } + }) + + test('a bunch of old tests', async () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) @@ -48,7 +77,7 @@ describe('eip712-secp256k1.car', () => { if (!entry.original) continue // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const original = car.get(entry.original) - const recalculatedFromOriginal = fromOriginal(original as Eip712) + const recalculatedFromOriginal = Eip712.fromOriginal(original as Eip712) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const node = car.get(entry.node) expect(node).toEqual(recalculatedFromOriginal) From e8adc9e733f76b2e3cacd73d71cbfa66044520c6 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Fri, 24 Nov 2023 18:37:49 +0300 Subject: [PATCH 097/110] skipped a bunch of old tests --- .../varsig/src/__tests__/eip712-secp256k1.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 44c6b3d5..b2f75751 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -63,13 +63,21 @@ describe('eip712-secp256k1.car', () => { if (!entry.original) continue // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const original = car.get(entry.original) - const varsigNode = Eip712.fromOriginal(original as Eip712) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const node = car.get(entry.node) + const originalFixedSig = klona(original) + if (Object.keys(original.signature).includes('r')) { + const r = uint8arrays.fromString(original.signature.r.replace(/^0x/, ''), 'hex') + const s = uint8arrays.fromString(original.signature.s.replace(/^0x/, ''), 'hex') + originalFixedSig.signature = + '0x' + uint8arrays.toString(uint8arrays.concat([r, s, [original.signature.v]]), 'hex') + } + const recoveredOriginal = await toOriginal(node) + await expect(recoveredOriginal).toEqual(originalFixedSig) } }) - test('a bunch of old tests', async () => { + test.skip('a bunch of old tests', async () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) From 259f9cf7c90728ea0914422645d56a9b02ef0426 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 18:54:01 +0300 Subject: [PATCH 098/110] tests and builds --- .../src/__tests__/eip712-secp256k1.test.ts | 111 ++++++------------ packages/varsig/src/varsig.ts | 5 +- 2 files changed, 40 insertions(+), 76 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index b2f75751..74fb497b 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -1,11 +1,8 @@ import { readFile } from 'node:fs/promises' import { expect, test } from '@jest/globals' -import { CARFactory } from 'cartonne' +import { CARFactory, type CAR } from 'cartonne' import { CID } from 'multiformats/cid' -import { BytesTape } from '../bytes-tape' -import { Decoder } from '../decoder' import * as uint8arrays from 'uint8arrays' -import { CanonicalizationKind } from '../canonicalization.js' import { Eip712 } from '../canons/eip712.js' import { verify, toOriginal } from '../varsig.js' import { klona } from 'klona' @@ -13,7 +10,7 @@ import { klona } from 'klona' const factory = new CARFactory() describe('eip712-secp256k1.car', () => { - let car, entries + let car: CAR, entries: Array beforeAll(async () => { const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) @@ -30,26 +27,44 @@ describe('eip712-secp256k1.car', () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access const node = car.get(entry.node) - console.log('time', node) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const verificationKey = - entry.signer.address || uint8arrays.fromString(entry.signer.publicKey.slice(2)) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call + entry.signer.address || + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + uint8arrays.fromString(entry.signer.publicKey.replace(/^0x/, ''), 'hex') - expect(await verify(node, verificationKey)).toEqual(entry.valid) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (entry.valid) { + // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access + await expect(verify(node, verificationKey)).resolves.toEqual(entry.valid) + } else { + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const verification = await verify(node, verificationKey) + // eslint-disable-next-line jest/no-conditional-expect + expect(verification).toEqual(false) + } catch (e) { + // eslint-disable-next-line jest/no-conditional-expect + expect(e).toBeTruthy() + } + } } }) - test('Create varsig ipld node', async () => { + test('Create varsig ipld node', () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access if (!entry.original) continue - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument const original = car.get(entry.original) const varsigNode = Eip712.fromOriginal(original as Eip712) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument const node = car.get(entry.node) expect(varsigNode).toEqual(node) } @@ -61,72 +76,20 @@ describe('eip712-secp256k1.car', () => { const entry = car.get(entryCID) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access if (!entry.original) continue - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - const original = car.get(entry.original) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - const node = car.get(entry.node) - const originalFixedSig = klona(original) - if (Object.keys(original.signature).includes('r')) { - const r = uint8arrays.fromString(original.signature.r.replace(/^0x/, ''), 'hex') - const s = uint8arrays.fromString(original.signature.s.replace(/^0x/, ''), 'hex') - originalFixedSig.signature = - '0x' + uint8arrays.toString(uint8arrays.concat([r, s, [original.signature.v]]), 'hex') - } - const recoveredOriginal = await toOriginal(node) - await expect(recoveredOriginal).toEqual(originalFixedSig) - } - }) - - test.skip('a bunch of old tests', async () => { - for (const entryCID of entries) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const entry = car.get(entryCID) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - if (!entry.original) continue - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const original = car.get(entry.original) - const recalculatedFromOriginal = Eip712.fromOriginal(original as Eip712) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument + const originalExpected = car.get(entry.original) const node = car.get(entry.node) - expect(node).toEqual(recalculatedFromOriginal) - const originalKlone = klona(original) - if (Object.keys(original.signature).includes('r')) { - const r = uint8arrays.fromString(original.signature.r.replace(/^0x/, ''), 'hex') - const s = uint8arrays.fromString(original.signature.s.replace(/^0x/, ''), 'hex') + const originalKlone = klona(originalExpected) + if (Object.keys(originalKlone.signature).includes('r')) { + const r = uint8arrays.fromString(originalKlone.signature.r.replace(/^0x/, ''), 'hex') + const s = uint8arrays.fromString(originalKlone.signature.s.replace(/^0x/, ''), 'hex') originalKlone.signature = - '0x' + uint8arrays.toString(uint8arrays.concat([r, s, [original.signature.v]]), 'hex') - } - await expect(toOriginal(node)).resolves.toEqual(originalKlone) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const varsig = new Decoder(new BytesTape(node._sig)).read() - if (varsig.canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error(`Not 712`) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const input = varsig.canonicalization(original.message) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - const signer = entry.signer - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - if (signer.publicKey) { - const verificationResult = await varsig.signing.verify( - input, - varsig.signature, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - uint8arrays.fromString(signer.publicKey.replace(/^0x/, ''), 'hex') - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,jest/no-conditional-expect - expect(verificationResult).toEqual(entry.valid) - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (signer.address) { - const verificationResult = await varsig.signing.verify( - input, - varsig.signature, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access - signer.address - ) - // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-member-access - expect(verificationResult).toEqual(entry.valid) + '0x' + + uint8arrays.toString(uint8arrays.concat([r, s, [originalKlone.signature.v]]), 'hex') } + + const originalRecovered = await toOriginal(node) + expect(originalRecovered).toEqual(originalKlone) } }) }) - diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 273cf7e2..14bb4865 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -23,11 +23,12 @@ export async function verify( // @ts-ignore const { canonicalization, signing, signature } = new Decoder(tape).read() + const toCanonicalize = klona(node) // @ts-ignore - delete node._sig + delete toCanonicalize._sig // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const signatureInput = canonicalization(node) + const signatureInput = canonicalization(toCanonicalize) // @ts-ignore return signing.verify(signatureInput, signature, verificationKey) From 329437be5a7b4d3779f45464cedb0926f909efdf Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 18:56:26 +0300 Subject: [PATCH 099/110] pacify linter --- packages/varsig/src/__tests__/eip712-secp256k1.test.ts | 8 ++++++++ packages/varsig/src/canons/jws.ts | 9 +++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 74fb497b..aa89c325 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -78,16 +78,24 @@ describe('eip712-secp256k1.car', () => { if (!entry.original) continue // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument const originalExpected = car.get(entry.original) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access const node = car.get(entry.node) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const originalKlone = klona(originalExpected) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access if (Object.keys(originalKlone.signature).includes('r')) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access const r = uint8arrays.fromString(originalKlone.signature.r.replace(/^0x/, ''), 'hex') + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access const s = uint8arrays.fromString(originalKlone.signature.s.replace(/^0x/, ''), 'hex') + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access originalKlone.signature = '0x' + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access uint8arrays.toString(uint8arrays.concat([r, s, [originalKlone.signature.v]]), 'hex') } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument const originalRecovered = await toOriginal(node) expect(originalRecovered).toEqual(originalKlone) } diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index 9b5cdf32..a0ed0ce4 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -25,7 +25,6 @@ const HASH_BY_KEY_TYPE: Record = { const toB64u = (bytes: Uint8Array) => uint8arrays.toString(bytes, 'base64url') const fromB64u = (b64u: string) => uint8arrays.fromString(b64u, 'base64url') - const SIGIL = MAGIC.JOSE // jose export const JWS = { SIGIL, prepareCanonicalization, fromOriginal } @@ -40,8 +39,10 @@ export function prepareCanonicalization( const protected1 = JSON.parse(uint8arrays.toString(protectedBytes)) const keyTypeFromProtected = findKeyType(protected1) - if (keyType !== keyTypeFromProtected) throw new Error(`Key type missmatch: ${keyType}, ${keyTypeFromProtected}`) - if (hashType !== HASH_BY_KEY_TYPE[keyType]) throw new Error(`Hash type missmatch: ${hashType}, ${HASH_BY_KEY_TYPE[keyType]}`) + if (keyType !== keyTypeFromProtected) + throw new Error(`Key type missmatch: ${keyType}, ${keyTypeFromProtected}`) + if (hashType !== HASH_BY_KEY_TYPE[keyType]) + throw new Error(`Hash type missmatch: ${hashType}, ${HASH_BY_KEY_TYPE[keyType]}`) const can = (node: IpldNode) => { // encode node using dag-json from multiformats @@ -61,7 +62,7 @@ export function prepareCanonicalization( export function fromOriginal(jws: string): IpldNodeSigned { const [protectedB64u, payloadB64u, signatureB64u] = jws.split('.') - const node = decode(fromB64u(payloadB64u)) as IpldNode + const node = decode(fromB64u(payloadB64u)) const protectedBytes = fromB64u(protectedB64u) const protected1 = JSON.parse(uint8arrays.toString(protectedBytes)) const protectedLength = varintes.encode(protectedBytes.length)[0] From 6a60f69c8af3d028cd4626ac42525e061141ee69 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 18:58:23 +0300 Subject: [PATCH 100/110] pacify linter pt2 --- packages/varsig/src/canons/jws.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index a0ed0ce4..b09cbf05 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -36,8 +36,10 @@ export function prepareCanonicalization( ): CanonicalizationAlgo { const protectedLength = tape.readVarint() const protectedBytes = tape.read(protectedLength) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const protected1 = JSON.parse(uint8arrays.toString(protectedBytes)) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const keyTypeFromProtected = findKeyType(protected1) if (keyType !== keyTypeFromProtected) throw new Error(`Key type missmatch: ${keyType}, ${keyTypeFromProtected}`) @@ -62,12 +64,14 @@ export function prepareCanonicalization( export function fromOriginal(jws: string): IpldNodeSigned { const [protectedB64u, payloadB64u, signatureB64u] = jws.split('.') - const node = decode(fromB64u(payloadB64u)) + const node = decode>(fromB64u(payloadB64u)) const protectedBytes = fromB64u(protectedB64u) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const protected1 = JSON.parse(uint8arrays.toString(protectedBytes)) const protectedLength = varintes.encode(protectedBytes.length)[0] const signature = fromB64u(signatureB64u) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const keyType = findKeyType(protected1) const hashType = HASH_BY_KEY_TYPE[keyType] @@ -81,6 +85,7 @@ export function fromOriginal(jws: string): IpldNodeSigned { protectedBytes, signature, ]) + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...node, _sig: varsig } } @@ -92,6 +97,6 @@ interface ProtectedHeader { function findKeyType({ alg, crv }: ProtectedHeader): number { if (!alg) throw new Error(`Missing alg in protected header`) const keyType = KEY_TYPE_BY_ALG_CRV[alg][crv || 'default'] - if (!keyType) throw new Error(`Unsupported alg: ${alg}, or crv: ${crv}`) + if (!keyType) throw new Error(`Unsupported alg: ${alg}, or crv: ${String(crv)}`) return keyType } From 6bcd60632e22d7aa7aafcecbe24c807c38a1f00f Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 24 Nov 2023 18:59:57 +0300 Subject: [PATCH 101/110] pacify linter pt2 --- .../varsig/src/__tests__/eip712-secp256k1.test.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index aa89c325..109fe6a0 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -42,15 +42,10 @@ describe('eip712-secp256k1.car', () => { // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access await expect(verify(node, verificationKey)).resolves.toEqual(entry.valid) } else { - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const verification = await verify(node, verificationKey) - // eslint-disable-next-line jest/no-conditional-expect - expect(verification).toEqual(false) - } catch (e) { - // eslint-disable-next-line jest/no-conditional-expect - expect(e).toBeTruthy() - } + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const verificationP = verify(node, verificationKey).catch(() => false) + // eslint-disable-next-line jest/no-conditional-expect + await expect(verificationP).resolves.toEqual(false) } } }) From 03318723e5d769da74901ee9efc7aaf3a4be8db8 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Sat, 25 Nov 2023 14:22:08 +0300 Subject: [PATCH 102/110] JWS test vectors just dropped --- .../varsig/src/__tests__/__vectors__/jws.car | Bin 0 -> 14461 bytes .../varsig/src/__tests__/jws-es256.test.ts | 208 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 packages/varsig/src/__tests__/__vectors__/jws.car create mode 100644 packages/varsig/src/__tests__/jws-es256.test.ts diff --git a/packages/varsig/src/__tests__/__vectors__/jws.car b/packages/varsig/src/__tests__/__vectors__/jws.car new file mode 100644 index 0000000000000000000000000000000000000000..5ca61b68e8062e38d287a51d4d7c85a879b044d0 GIT binary patch literal 14461 zcmeI3c~nw)|Hrk-Dc8zoG_{nmG@IN|z@;ezK?GSuT!5O0Kx{=J`wHe9qC!`}O^NUVOjz z25tltad8RCdDY)CT#St4tzoOud|FQw)~&nN`gH6<`8RA!mxZe6htG26NQBOT z&N#Xc5*6k=TjH?R_$Y17eN!_*(&&i7o;5$4b=wR=t1KL+dz9aA4c!+@k1RT4Pa0jF z-#sa7(+dBmr+KB@H;_;9N06njts+DTfHHvu%3`a(rD!kJ$aDjrqvQ+hjIkRBy}Ohg zhXvNPJ7CM7nruk<9x|jG?E;NQ})BoE1z!5oFOCu2}n-;X@L5c(sK3?tQ#O!j)1a z1W*OC7$TMS@2DxsXcaXQhae~8klKHTi%Ddnxd{;x4+1e*%JK;f2@|u4fFd}F7lV!s z@x@}0JQ9xt`r$wg2TqAnlA}10m}I7m&GVCl(Ico-Hje4%Bae&n#j-FV6t2LV8YOJ( zjXU@1FKJWh;f>?a`)chcAFb)$CfOF2+w@!MwWlua4q5C?d|4bH(8B~p2av>(fc#E@ zHg_4I*bt{>3lxAnPJ$M&FhIjV4<6#MAan9BalYWO(?-6^!6>82wB(y!)+Os2@S?Or zakdzsGU51g7Ev&w(2ElpWebuIz5q-t~z%g*?S##9ui0EM88Bhmj)P%-PcCIZ9P z*IYg5{9LPnnM=ZvHScs4E8mg(6N7PmuLy2NQ9WA{b8_x-QG-pJA5Q zodP{df3>_TkemMcl7?NMNfWnX$!3hR#D#ir6E znyOn-(vmT%c(*zAO**^M)nzeh>$EQTKe0z~xV-m0O2_-K9D1^i#MZ?-@AMnglLFuQ z%3J7$4wtE%5su+u1q-hj@D4?X0q^fx6-PVJ{ubV$;y>EE;O)NqB4@?shZWx%mj*A$ zdsxlA;V89~D7%rK`R&&2;+OsBo%ntx51RD}r*KB^6uwL_J5H>`yh(qVND9xgE^Iiv zeU1A;t7uUgI^T6T`=+`X@b&&@jNIkDA4nl*-iUhJ+fyst%D-G#oIQQ1>8q@1H5)`b zZ@-nkLY2=mz!@rh9nNYeGN@F7G9gUsSTIlqlPls90A2#Xhlv0VA5i=q-5e_V^SyKr z`t?MmYBNyl*HxY4`SS$?;V`w6V7pWL{JikNbo*!5`cHQZV(z$i-F(;w#_2rUC6kPM(Ufsy2E6BC zYHi-g>^ngRxqmL41-;2`;`nA=*zCO9u`%8Cc!_B>Gf{l@S#>V{a8*-B`1DaY^{d|r zCrAwV+}Ou(1LA39z(*mcc(a5k42cv*iRDS7NUT^26Dg-jH6lL^84H(j0+5l2uy9`= zA=2AhNLG5IXpvD21d8B+Vu(ZikxUXGi=c9R#2#?wd)-9v0jr_QPQMyjYi*JPyq4f@ zB>(7_D{8J`z%#zcntU9&;z?9+ zd{F7&`^2hW;9upsRb@VpHfUE+7j7_p`(c}ZSCqJysXGGN=tZ}vSL`#V*f@n0y~y0C zm@hI~y>3;Z<;jr)i}IuPC$Pl6Fa4>*r2ZbUmR7q2{&tV$5p0Xe&J;)|h`_%|^;n1K6R^ zFo3-us`>F@hYDY(n(T6Z%=2|im1Lh_Hb%CMk#9amIQOaGY9>=b3S!|2{fC|Sei&<2 z^V7{=ooaHP$;>3KZKobO!ena;9P^hYO2qORw7PDKk-sgl+93HXRrAw~Tb*jYnVN2j z$u&o{ab~iNbKH!7dbs>%`|Y74y@U%ZoYr9$8Q=^R{zp^Ik54yL^t!2B%?}{^dy0c$ zC+iNF-83mWvvZnd;p#|2@Li{suA1oD{?i?UXzktH{P$GmW9ew;*}3@P)+5FycsV|r z9(6Cf=&4F5JyCyb$AxW;5##rAW}8khEuZjkL{PfY_c8)k#-@vY+w!vY(3iJXZUdQ? z^U3B=swv?SmNRlTs@HyTd^-gLWLRwxViuLPRF`Jr93>~)_kP&QS-6Nuns$Tob5l(j zjz`0ZaAJP2KY)~|qY=b-m7gzP#^tg_Nnwhxcq*Sx^a~LQXtDBO9zl+bz_CFYIXO8p zB19Du5XlKolzIRf1SWw)kHcS0%YoAT>lO@&p74y88 z@wO|4nCv}vbk~Vj#)mcDTVU^Jj7<#tc5-pYz>@k<6#t(|fhBf*=`P~${e5o4cjpmn zW#=5|6projy;eO1Sl5TgelQB`uB-Uxbr-EDzV9wNmfNPhSX#mm?_LJCh!^a$7=^%h zE{cwaMVef{R9G);s5W333Jn96`z(qBU>Pb~od>R(Gv=tuNwYO&|%@Hd%@`0oAy#B-cXPS5W z^%(2L0droWn$S*Zs}y&x?9I%{yxVn~{&dCo&=o!9UvI;FQvqv|t%2pC2a|q^57=+6 zT2wHNIacl8;M|pG0i}7n3&N+U?wHPKDb7yYhRm+cmoAN&GIJ^K#R$8M^c~@D{~e7O z&i3Zx;$o44q*xym4kr*1qcuKClscA}Nb_NH8Brb{5kzk=f=wnw$Kwbh4ILZDmGgwM zC>$=5NKsR{WD*|%3S;20VgM@^6OuT5ELx{|ZcQAo7!ya=Y<$_#o8Hfd6u{kvbsSky zpE2`^Pry~nV+NWBg$AVN6;G&nk@NbToB2gTRswHAs%E_7A+zwNR5Dl6Q)P)<2#=dLbr%H;&TXd+kfpPD~ zl5r_N*6v;G=xy#AwSDCbxcm0vIm1pJ8CX((q~55ayaTVuSqk>WxmzdL1G{<;Mo$j$!pT6q9EnNXKUZQ2;I^+ zF66V+yiYR=b()8GeLwp9Ij%HVHzsjXR4Hr6h9UL4XAXYrHa%mZa7OMU16rZN|5%#W ze|VviebsBV4dn521jwvwy=60oI6XDADH_~%+fHL=d9+`x2q;J?;AFEbc#d!$xG>De~R0fIO^J? zix#7!pA0n{Y4qn}*wA~_ysu!#&GNH~sol@;A#abK+yxfR;Md&wKGfYh$LUKPYpBNf zyZ=0E{#-Wz2}BY-fW!q$PnL7>zN~OG8lI@6VnU)pbz%aCO^pmy$I4X+1S~Hap(aJq zk$xy9i7lba(2`%gCI4Miz34D6s%OEQt_e_y$Qi0GP(;Vo zCPl`0Aub=Cl(K%>!ohRSoVl&y_7Fs0D^i++U)+4VX_|Oa!SdjX%m3h%8j26y3d((S zwRN|bpU5aiX;->`WoXB!pmOSbn#5y!bkeKW5Jc9G2NLAtn)=s=-pI2p+dMOa+Ow!W z)%&rjU8~FaAcxCy=C_I31g%f6w+}4zKl0bVF7z*R6n$!&=ANk3LgVvp)?F|o%9+k7 zFXq9Ql-6{uUtWB*=6R9Lng6xT0klo6G{0|~x|Nlm04_7Pm_G6=uQzICVf8T;2!<4; zG6nUh>5F;^B8H#+}1lrl-`JonJxh&|+}UAAr1y*t-$?AqBr zu54H3iDhwn75!gXKm2>s^sie?&<;}B>mcD4W|R|BLTINKZU1Xm)8T?E{#y>zJx$Hd zGOK;uRdZNlJn=KUwexdTjs?A+sfZd#;jmVEr0m5W*v0TvKQ<3|x_^7Ko%-O2)>rN` zI}D~06mcI(;Xme5f}-Z|kiI;%w2A+2!lsIpB94!T_HiOm`yv*2_eu!J5)>ey%+fy~ z0`-iGz7Xoc3Vk8e9qIZ)sC#|&g-|y}>IJ7LdLW>f5)z5bA4%z7R@L^@UIqS6>L#UO@eHO#=XB s@45nba_!GpxDep`Ad&C@0N^7K{&F5i7AMm_*(Cv!d!O#&iB-z~0|zrasQ>@~ literal 0 HcmV?d00001 diff --git a/packages/varsig/src/__tests__/jws-es256.test.ts b/packages/varsig/src/__tests__/jws-es256.test.ts new file mode 100644 index 00000000..4b30cb23 --- /dev/null +++ b/packages/varsig/src/__tests__/jws-es256.test.ts @@ -0,0 +1,208 @@ +import { readFile } from 'node:fs/promises' +import { expect, test } from '@jest/globals' +import { CARFactory, type CAR } from 'cartonne' +import { CID } from 'multiformats/cid' +import * as uint8arrays from 'uint8arrays' +import { JWS } from '../canons/jws.js' +import { verify, toOriginal } from '../varsig.js' +import { MAGIC } from '../magic.js' +import { BytesTape } from '../bytes-tape.js' +import { klona } from 'klona' +import * as varintes from 'varintes' + + +const factory = new CARFactory() + +describe('jws.car', () => { + let car: CAR, entries: Array + + beforeAll(async () => { + const carFilepath = new URL('./__vectors__/jws.car', import.meta.url) + const carBytes = await readFile(carFilepath) + car = factory.fromBytes(carBytes) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const root = car.get(car.roots[0]) + if (!root) throw new Error(`Empty root`) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + entries = root.entries as Array + }) + + + test.skip('Verify signatures', async () => { + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access + const node = car.get(entry.node) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const verificationKey = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call + entry.signer.address || + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + uint8arrays.fromString(entry.signer.publicKey.replace(/^0x/, ''), 'hex') + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (entry.valid) { + // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access + await expect(verify(node, verificationKey)).resolves.toEqual(entry.valid) + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const verificationP = verify(node, verificationKey).catch(() => false) + // eslint-disable-next-line jest/no-conditional-expect + await expect(verificationP).resolves.toEqual(false) + } + } + }) + + test.skip('Create varsig ipld node', () => { + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + if (!entry.original) continue + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument + const original = car.get(entry.original) + const varsigNode = Eip712.fromOriginal(original as Eip712) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument + const node = car.get(entry.node) + expect(varsigNode).toEqual(node) + } + }) + + test.skip('Recover original from ipld node', async () => { + for (const entryCID of entries) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const entry = car.get(entryCID) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + if (!entry.original) continue + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument + const originalExpected = car.get(entry.original) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access + const node = car.get(entry.node) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const originalKlone = klona(originalExpected) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access + if (Object.keys(originalKlone.signature).includes('r')) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const r = uint8arrays.fromString(originalKlone.signature.r.replace(/^0x/, ''), 'hex') + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const s = uint8arrays.fromString(originalKlone.signature.s.replace(/^0x/, ''), 'hex') + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + originalKlone.signature = + '0x' + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + uint8arrays.toString(uint8arrays.concat([r, s, [originalKlone.signature.v]]), 'hex') + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument + const originalRecovered = await toOriginal(node) + expect(originalRecovered).toEqual(originalKlone) + } + }) +}) + + +// import * as jose from 'jose' +// import { generateKeyPairSync } from 'node:crypto' +// import { encode, decode } from '@ipld/dag-json' +// import { pipeline } from "node:stream/promises"; +// import * as fs from "node:fs"; + +// async function createVectors() { +// const car = factory.build() +// const entries = [] +// await gen('ec', { namedCurve: 'secp256k1' }, 'ES256K') +// await gen('ec', { namedCurve: 'P-256' }, 'ES256') +// await gen('ed25519', {}, 'EdDSA') +// await gen('ed25519', {}, 'EdDSA', 'ed25519') +// await gen('ed448', {}, 'EdDSA', 'ed448') +// +// console.log(entries) +// +// car.put({ +// entries, +// canonicalization: 'jws', +// hash: ['sha2-256', 'shake-256'], +// signature: ['es256', 'secp256k1', 'ed25519', 'ed448'] +// }, { isRoot: true }) +// +// await pipeline(car, fs.createWriteStream("./jws.car")); +// +// async function gen(name, opt, alg, crv) { +// const kp = generateKeyPairSync(name, opt) +// const {x, y } = kp.publicKey.export({ format: 'jwk' }) +// const verificationKey = y ? +// uint8arrays.concat([ +// [0x04], +// uint8arrays.fromString(x, 'base64url'), +// uint8arrays.fromString(y, 'base64url') +// ]) : +// uint8arrays.fromString(x, 'base64url') +// +// const payload = JSON.parse(uint8arrays.toString(encode({ testLink: CID.parse('bafyqacnbmrqxgzdgdeaui') }))) +// const jwt = await new jose.SignJWT(payload) +// .setProtectedHeader({ alg }) +// .setIssuedAt() +// .setAudience('urn:example:audience') +// .setExpirationTime('2h') +// .sign(kp.privateKey) +// +// +// const node = JWS.fromOriginal(jwt) +// +// const entry1 = car.put({ +// valid: true, +// signer: { verificationKey }, +// node: car.put(node), +// original: car.put(jwt) +// }) +// const nodeKeccak = klona(node) +// let tape = new BytesTape(nodeKeccak._sig) +// tape.read(1); tape.readVarint(); +// const hashPosition = tape.position +// nodeKeccak._sig.set([MAGIC.KECCAK_256], hashPosition) // TODO - fix +// const entry2 = car.put({ +// valid: false, +// error: 'Invalid hash code', +// signer: { verificationKey }, +// node: car.put(nodeKeccak), +// original: car.put(jwt) +// }) +// const jwtInvalid = jwt.substring(0, jwt.length - 10) + 'abc' + jwt.substring(jwt.length - 7) +// const entry3 = car.put({ +// valid: false, +// error: 'Invalid signature', +// signer: { verificationKey }, +// node: car.put(JWS.fromOriginal(jwtInvalid)), +// original: car.put(jwtInvalid) +// }) +// const invalidProtectedBytes = uint8arrays.fromString(JSON.stringify({})) +// const invalidProtected = uint8arrays.toString(invalidProtectedBytes, 'base64url') +// const jwtMissingAlg = invalidProtected + jwt.substring(jwt.indexOf('.')) +// const nodeMissingAlg = klona(node) +// const protectedLength = varintes.encode(invalidProtectedBytes.length)[0] +// tape = new BytesTape(nodeKeccak._sig) +// tape.read(1); tape.readVarint(); tape.readVarint(); tape.readVarint(); +// const protLenPos = tape.position +// const klonLength = tape.readVarint() +// const signature = nodeMissingAlg._sig.slice(tape.position + klonLength) +// const newVarsig = uint8arrays.concat([ +// nodeMissingAlg._sig.slice(0, protLenPos), +// varintes.encode(invalidProtectedBytes.length)[0], +// invalidProtectedBytes, +// signature +// ]) +// nodeMissingAlg._sig = newVarsig +// const entry4 = car.put({ +// valid: false, +// error: 'Missing alg in protected header', +// signer: { verificationKey }, +// node: car.put(nodeMissingAlg), +// original: car.put(jwtMissingAlg) +// }) +// entries.push(entry1, entry2, entry3, entry4) +// } +// } + + From 7c9534ee238219551bc77bc741484d6351604c60 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Wed, 10 Jan 2024 17:59:22 +0100 Subject: [PATCH 103/110] JWS Canon test --- .../varsig/src/__tests__/canons/jws.test.ts | 41 +++++++++++++++++++ packages/varsig/src/canonicalization.ts | 1 + packages/varsig/src/canons/jws.ts | 4 +- 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 packages/varsig/src/__tests__/canons/jws.test.ts diff --git a/packages/varsig/src/__tests__/canons/jws.test.ts b/packages/varsig/src/__tests__/canons/jws.test.ts new file mode 100644 index 00000000..44d64c8c --- /dev/null +++ b/packages/varsig/src/__tests__/canons/jws.test.ts @@ -0,0 +1,41 @@ +import { fromOriginal, prepareCanonicalization } from '../../canons/jws.js' +import { BytesTape } from '../../bytes-tape.js' +import * as uint8arrays from 'uint8arrays' +import { CanonicalizationKind } from '../../canonicalization.js' + +//const testJws = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const testJws = 'eyJhbGciOiAiRVMyNTYifQ.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + +const expectedHashInput = uint8arrays.fromString( + testJws.slice(0, testJws.lastIndexOf('.')) +) + +test('Encode eip712 message', () => { + // @ts-ignore + const node = fromOriginal(testJws) + + expect(node._sig.length).toEqual(56) + delete node._sig + expect(node).toEqual({ + sub: '1234567890', + name: 'John Doe', + iat: 1516239022 +}) +}) + +test('Canonicalize ipld eip712 object', () => { + // @ts-ignore + const node = fromOriginal(testJws) + const tape = new BytesTape(node._sig) + tape.readVarint() // skip sigil + const keyType = tape.readVarint() // skip key type + const hashType = tape.readVarint() // skip hash type + tape.readVarint() // skip canonicalizer codec + const can = prepareCanonicalization(tape, hashType, keyType) + expect(can.kind).toEqual(CanonicalizationKind.JWS) + expect(tape.remainder.length).toEqual(32) + // @ts-ignore + delete node._sig + const sigInput = can(node) + expect(sigInput).toEqual(expectedHashInput) +}) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index c391c008..4f4f57f6 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -10,6 +10,7 @@ import type { SigningKind } from './signing.js' export enum CanonicalizationKind { EIP712 = MAGIC.EIP712, EIP191 = MAGIC.EIP191, + JWS = MAGIC.JOSE, } type IpldNode = Record diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index b09cbf05..aea05143 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -48,7 +48,7 @@ export function prepareCanonicalization( const can = (node: IpldNode) => { // encode node using dag-json from multiformats - const payloadB64u = toB64u(uint8arrays.fromString(JSON.stringify(encode(node)))) + const payloadB64u = toB64u(encode(node)) const protectedB64u = toB64u(protectedBytes) return uint8arrays.fromString(`${protectedB64u}.${payloadB64u}`) } @@ -96,7 +96,7 @@ interface ProtectedHeader { function findKeyType({ alg, crv }: ProtectedHeader): number { if (!alg) throw new Error(`Missing alg in protected header`) - const keyType = KEY_TYPE_BY_ALG_CRV[alg][crv || 'default'] + const keyType = KEY_TYPE_BY_ALG_CRV[alg]?.[crv || 'default'] if (!keyType) throw new Error(`Unsupported alg: ${alg}, or crv: ${String(crv)}`) return keyType } From f9d7f0840832f2110b519cdb01b109b404858f2c Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Tue, 6 Feb 2024 19:23:13 +0000 Subject: [PATCH 104/110] JWS work --- .../varsig/src/__tests__/__vectors__/jws.car | Bin 14461 -> 14461 bytes .../varsig/src/__tests__/canons/jws.test.ts | 7 +- .../varsig/src/__tests__/jws-es256.test.ts | 225 +++++++++--------- packages/varsig/src/canonicalization.ts | 3 + packages/varsig/src/signing.ts | 6 + packages/varsig/src/signing/p256.ts | 28 +++ 6 files changed, 154 insertions(+), 115 deletions(-) create mode 100644 packages/varsig/src/signing/p256.ts diff --git a/packages/varsig/src/__tests__/__vectors__/jws.car b/packages/varsig/src/__tests__/__vectors__/jws.car index 5ca61b68e8062e38d287a51d4d7c85a879b044d0..6d3e9b9170890ae64d004757305b153686f56227 100644 GIT binary patch literal 14461 zcmeI3c~nzpw}(Lx<#S|Ei%>v{pdbh_OoCzvBqSsOLLdPL2+1KiNl3^@0C57NC?K{n zIH6E+LO?4+t>TCxgQ6gaSQV8P1qaF?s90|#uH4VGT?bgwdROmXgsi<5ocx}5?zl18bJf)Ox~5SwL$4Vnn`KoF8xIC$GE27rb`39tP5iFI4np+wMjAr` z2!tSytA17eL;2O}jHUY!yEZ#anUCA#d$lnL9_BhJZOgXi^hK-d9=v~>N27}ABC&wG z7?4sUqd34~`R^dWr2#ZR8Z{fZxb+APq>AJ(cuDk7A;_SR;p@zG8(P(;#hFn#470^% zUI8!%N3-}TwU1k`N!IHfVzrKs?_Xd$Rcrn%bZV>Y;qrNxCwFSYUOYJQ8!vuExwOt+ z&l^@AmRXT={r<_tobgVA?rpbLonndrp$G?Z+09l2`IV|^=g_G_dW3#+L0^{FBD^OphT!QHOCaHem`Id;2omFn;ReNov*aHb8YQBkr5xW6@1r@K@JIp& z14e@(6lS#i???%;h(&moKb$}cjE(TO^`ynJ;COo&gXqhpiV>)Y2$~SbfTMkgbZ?dz ziG~LWc~bva0g{9hFLLshhA*}9#=&il!4~OzLrV8H5Yq!PV=3*Y2l+w2k2ZL9Z-@4g$5v@azJ`t zggkUEAjsj#!2%;d8dW5x%Lj;|AT+gnUZ*0ni*l_S%=&>VbMJU1FXOHiXH}c9n|f;> zC1Z_T3plABsW%^N(49am)I)W;mK-c8S-Pc^u;n;F3){ow2|xzOrE--26I93wsBL!! zv%gQvxScCQ7VymcZ8QQR;a}L`^qpQ5~Qt#-XHvC(mem@jhovxsj)MZNk%gRTfVL-5ut;&ONjBaUEThpS3@MWw7D&CsS`OTrIm` z!%@<^)kne-cBd3S%6saz>y*I#M%ivhqr`t-f1-xdKK3UC?3GD3TaI2Lv2k}d{<00p z$DX4wrm4+hXl_P@&b{Ae)~*6O6dEe9e`r%w`6T;CV229-^w4Vmu_xI0M7vIr&ynV( z&#mCs;_CBg&Gmic?e}hV_aW&6BZnRPaTt$_8^ig#ZD{@;zDOxE+O_P$X<0(m{@{3@ z@VM90O9efmf~w5T2LeL0rB8uHcK?oXn>zG?Xw}cu+mYP5fNgX&wPvGs)ETaz&@ET| z(7Y+beByxH0F@31{)tu&6}@6A z56%cKlXlFwa>1XLYx;Ehv~`r_r8-78ZdSDXCkN5K=E%tDCc~epf7;6Fo&gB6__mC- zCts1L_q+(0ve@q_X8(!y)wbWX{yzR(1xf3;2o*FD<9u^hFq*ivvTmw|4LdARoQxJ< z)xR0x+S~57895nBHK$);?vHyNWcqHLYoBm=&Y>OA`Qzh^x?fl}yCaA-eFr~nFJ5MGSta#1^kN5J~TBAmZ8nw zAxlWtjWlgzw-&kuh!*a3M_t!aQB5c`pj6Y;c01=nR^eqQkDn$leECakuAqE3TdjUI zc^5w|Pyd#+<{wQGDnkXc4lBg?z2%rAb1rGXoy}48_ov;-INJD1$mLkN?3pi4aev|n8n z88#eFA?UFQtIL6V7U;Hz-*n`yQehbi4HcG$EsCRH87fp%T zzbBPQRcM6@|1){uDBy)kRxvVPJposB-R*HU!MGt^ZB1Ni0cRX($!u})eDl*rUl+_8 zIlO^ny$;)(m^nSz4%^oF{-}yAo;oY_`TeEZ0 zOL}pXSa>U^Umd;y-mx9&9@IM#O7m7xrp*e=oa6Vjp=`X7$V{?4Kw~0VQq&y$aK|IP z%u@3IjYf=)l{kqh6htV&j_g6=JK0GiC{$GJA{184bo3!p{Csht3?>!hFQ9m{?Ib}M zawr8KPPd8lmqD=Oh!ZYh4 zjprSg<;*`^SE^lYN=TWZwNpj&pwNKSyx-qeXf|d<{0t-+;%R2(CJXf)?{w!HkK1C3 z$$Ly@ng3DqpfXf2+oHL|R@c~?*kQSLhyOw9;T@LFo>uA7n=M&;etWzu%58Ma{+Z^D z9kYtj_`tF@9`kPP;EGLcOQ$Zs7nq%beZ1zn$cScE_FU&-$6b|X19jmlqjC6}H&#h? zs4=R7*8UKWe10%Q-y)yA^Ca$0Hu)hjd$Z_JSeCi^=#u&~%^P)6V422ny32WOKr|B* zye8wtuJfKtP4Iko>mbZ^e85s(yvx^Dq6GiG?jqN`kKIMV@-MY-iTkEy)Hk<{8-G~; zn0sVddnhsAWE{r7KvuHSAX|lHC^S@99=_&{oMotN6`GeJ>8zU+YEcI(KAFxLu$q4> zI)9D6ExZ4$%!=V0vF60cS^h808#TU=!%?Am>Xwe~twplj%sjX6k7xIrNS5%{7c^yy zLZVuHx}F)Qe36JV;Q%A6}!m0dS7#V=3jY#G2fb+RYTB&*O0 z75?Yayph8Tm2B0J)iy7fIxUmjZ7e z+RdB($-%LYS^NfRVr}KmbALLUvOZRF%u*ITzO`NFF3o)4sAgQa#8@*(?YLglDOs=O z%1J-f_9b3Oe)0fmyFMDz(c5z0_!*|;uRSk!XruP@ueGqH{OJ2%X>9aMXog#M>+}cF2N)ZmM<&9KgQ96ZpY=j5Cnj?Sc(;W zJUeDxvW?Tz<%^#rq+jM~_7n)-vWi_h-`u$TV$sdlSFTy9m>v`zP^O2_Y!vbZu0N>Y z-^jJ%Jk9sznwlDE-{hO?FLXYs(fwuIvc_Ot%Jr3YhDXa%OY729Iug{CtIW?~yWl^U zrMCBM`d6femVMSex&c)MZK+PBlfN<6rnWU&B{XpjejiQ05(Zb;f?J7P2BQ&fUq4 zU;0xL+gkdrsxZIAsIAnb?ZwD>$9|m2@_oaxo5BjA47=@OhpV1`Uw7f9oC?<`1I#U# z-`$UQsV-PhnPRpvCfH!Hu7F97abo7#dnzSp^<7^1;3Rv4uFR*>MOz?fJ&)J1@?v~H6 zxQ^^=>`JcwPx=j)(Dm!|I`-l*aCPt^DfrR)&bHa`Yr2m7<)xL|&s}Z-f6qLi{_Xvc zf_sHsh?nWQm-X5yS?4lObiH0_p!U~_`gllD&#E0r*l!foys{?6RZ?GUjG&*AWj1HT zd(7IYb`dZ(UlKzG$X-NWYzR&4FJ*c-;R2mp1Y|x@ z91|^u2YSTtoTWGk=*)yU*#%*R4xTP_N;C`4^Wo4D4iq>V;bp_8lLFax2r?HLgyMn> zK*C0|NCYodh?MLU?#&JlMZqGuHXKKP54OmGDxkx}&R*UU4iSg)vY`h@;%q3w2u}wJ zk&SpT1Q@UTY0RM{uFX7ZR(4x%hozOdi|4`Bme&ji5u2Y!i0-R6E)pZ}PM5!vOjqc-yLQpu+ny6WCvNhT`zuk0;-FL|mNF zqV`9(h2lf8f&$u)1=PD2KO^JY4jyhaI`u2B?gxC|&I{R17BbnKwQ3`u4!j74!# ziDJA~aM4uB^7EcWo|5Osx{`l=0>V{R78H*vD{I)={W(__6gP?;ze!&kBPzm4z%qw< zAsG_vUjEbXR#+eRF{kXfO*g2ua2ff^TK&Jr^nc%Cf_9MlAqTnDS|EASU%F&MoJO*N z-H{L+AIMp<{aZ$NqOHDD6SpBo-TDi>!}Cj34%yIWI{xIA%5M7Odl#e|tuFTNNycqe z!#KZBsLBj9e{bt~d%McofgJ3CnlPNFmJZNAOxO)fA(INTkv~oZ%3rX7 z53d*iS0n%d;SS|9B2bUHC<~z;u22?2-I%T{gu3NdSqOE1q_Pm|wmoGb)ICwkLa5tO zl!Z`-`^rM7GiPNX)PbI|5b7*NSqQZ)tt^Dv3sn|EZ6YZPp}w9g3!%PNC<~z!Rapo% zag~Ko?FCd$*F*pi4y`MI7A615wr2ozn1h483jomJaOWr*mCNJGAMWCSu|p4d(OA*K F{{d}DTw4GD literal 14461 zcmeI3c~nw)|Hrk-Dc8zoG_{nmG@IN|z@;ezK?GSuT!5O0Kx{=J`wHe9qC!`}O^NUVOjz z25tltad8RCdDY)CT#St4tzoOud|FQw)~&nN`gH6<`8RA!mxZe6htG26NQBOT z&N#Xc5*6k=TjH?R_$Y17eN!_*(&&i7o;5$4b=wR=t1KL+dz9aA4c!+@k1RT4Pa0jF z-#sa7(+dBmr+KB@H;_;9N06njts+DTfHHvu%3`a(rD!kJ$aDjrqvQ+hjIkRBy}Ohg zhXvNPJ7CM7nruk<9x|jG?E;NQ})BoE1z!5oFOCu2}n-;X@L5c(sK3?tQ#O!j)1a z1W*OC7$TMS@2DxsXcaXQhae~8klKHTi%Ddnxd{;x4+1e*%JK;f2@|u4fFd}F7lV!s z@x@}0JQ9xt`r$wg2TqAnlA}10m}I7m&GVCl(Ico-Hje4%Bae&n#j-FV6t2LV8YOJ( zjXU@1FKJWh;f>?a`)chcAFb)$CfOF2+w@!MwWlua4q5C?d|4bH(8B~p2av>(fc#E@ zHg_4I*bt{>3lxAnPJ$M&FhIjV4<6#MAan9BalYWO(?-6^!6>82wB(y!)+Os2@S?Or zakdzsGU51g7Ev&w(2ElpWebuIz5q-t~z%g*?S##9ui0EM88Bhmj)P%-PcCIZ9P z*IYg5{9LPnnM=ZvHScs4E8mg(6N7PmuLy2NQ9WA{b8_x-QG-pJA5Q zodP{df3>_TkemMcl7?NMNfWnX$!3hR#D#ir6E znyOn-(vmT%c(*zAO**^M)nzeh>$EQTKe0z~xV-m0O2_-K9D1^i#MZ?-@AMnglLFuQ z%3J7$4wtE%5su+u1q-hj@D4?X0q^fx6-PVJ{ubV$;y>EE;O)NqB4@?shZWx%mj*A$ zdsxlA;V89~D7%rK`R&&2;+OsBo%ntx51RD}r*KB^6uwL_J5H>`yh(qVND9xgE^Iiv zeU1A;t7uUgI^T6T`=+`X@b&&@jNIkDA4nl*-iUhJ+fyst%D-G#oIQQ1>8q@1H5)`b zZ@-nkLY2=mz!@rh9nNYeGN@F7G9gUsSTIlqlPls90A2#Xhlv0VA5i=q-5e_V^SyKr z`t?MmYBNyl*HxY4`SS$?;V`w6V7pWL{JikNbo*!5`cHQZV(z$i-F(;w#_2rUC6kPM(Ufsy2E6BC zYHi-g>^ngRxqmL41-;2`;`nA=*zCO9u`%8Cc!_B>Gf{l@S#>V{a8*-B`1DaY^{d|r zCrAwV+}Ou(1LA39z(*mcc(a5k42cv*iRDS7NUT^26Dg-jH6lL^84H(j0+5l2uy9`= zA=2AhNLG5IXpvD21d8B+Vu(ZikxUXGi=c9R#2#?wd)-9v0jr_QPQMyjYi*JPyq4f@ zB>(7_D{8J`z%#zcntU9&;z?9+ zd{F7&`^2hW;9upsRb@VpHfUE+7j7_p`(c}ZSCqJysXGGN=tZ}vSL`#V*f@n0y~y0C zm@hI~y>3;Z<;jr)i}IuPC$Pl6Fa4>*r2ZbUmR7q2{&tV$5p0Xe&J;)|h`_%|^;n1K6R^ zFo3-us`>F@hYDY(n(T6Z%=2|im1Lh_Hb%CMk#9amIQOaGY9>=b3S!|2{fC|Sei&<2 z^V7{=ooaHP$;>3KZKobO!ena;9P^hYO2qORw7PDKk-sgl+93HXRrAw~Tb*jYnVN2j z$u&o{ab~iNbKH!7dbs>%`|Y74y@U%ZoYr9$8Q=^R{zp^Ik54yL^t!2B%?}{^dy0c$ zC+iNF-83mWvvZnd;p#|2@Li{suA1oD{?i?UXzktH{P$GmW9ew;*}3@P)+5FycsV|r z9(6Cf=&4F5JyCyb$AxW;5##rAW}8khEuZjkL{PfY_c8)k#-@vY+w!vY(3iJXZUdQ? z^U3B=swv?SmNRlTs@HyTd^-gLWLRwxViuLPRF`Jr93>~)_kP&QS-6Nuns$Tob5l(j zjz`0ZaAJP2KY)~|qY=b-m7gzP#^tg_Nnwhxcq*Sx^a~LQXtDBO9zl+bz_CFYIXO8p zB19Du5XlKolzIRf1SWw)kHcS0%YoAT>lO@&p74y88 z@wO|4nCv}vbk~Vj#)mcDTVU^Jj7<#tc5-pYz>@k<6#t(|fhBf*=`P~${e5o4cjpmn zW#=5|6projy;eO1Sl5TgelQB`uB-Uxbr-EDzV9wNmfNPhSX#mm?_LJCh!^a$7=^%h zE{cwaMVef{R9G);s5W333Jn96`z(qBU>Pb~od>R(Gv=tuNwYO&|%@Hd%@`0oAy#B-cXPS5W z^%(2L0droWn$S*Zs}y&x?9I%{yxVn~{&dCo&=o!9UvI;FQvqv|t%2pC2a|q^57=+6 zT2wHNIacl8;M|pG0i}7n3&N+U?wHPKDb7yYhRm+cmoAN&GIJ^K#R$8M^c~@D{~e7O z&i3Zx;$o44q*xym4kr*1qcuKClscA}Nb_NH8Brb{5kzk=f=wnw$Kwbh4ILZDmGgwM zC>$=5NKsR{WD*|%3S;20VgM@^6OuT5ELx{|ZcQAo7!ya=Y<$_#o8Hfd6u{kvbsSky zpE2`^Pry~nV+NWBg$AVN6;G&nk@NbToB2gTRswHAs%E_7A+zwNR5Dl6Q)P)<2#=dLbr%H;&TXd+kfpPD~ zl5r_N*6v;G=xy#AwSDCbxcm0vIm1pJ8CX((q~55ayaTVuSqk>WxmzdL1G{<;Mo$j$!pT6q9EnNXKUZQ2;I^+ zF66V+yiYR=b()8GeLwp9Ij%HVHzsjXR4Hr6h9UL4XAXYrHa%mZa7OMU16rZN|5%#W ze|VviebsBV4dn521jwvwy=60oI6XDADH_~%+fHL=d9+`x2q;J?;AFEbc#d!$xG>De~R0fIO^J? zix#7!pA0n{Y4qn}*wA~_ysu!#&GNH~sol@;A#abK+yxfR;Md&wKGfYh$LUKPYpBNf zyZ=0E{#-Wz2}BY-fW!q$PnL7>zN~OG8lI@6VnU)pbz%aCO^pmy$I4X+1S~Hap(aJq zk$xy9i7lba(2`%gCI4Miz34D6s%OEQt_e_y$Qi0GP(;Vo zCPl`0Aub=Cl(K%>!ohRSoVl&y_7Fs0D^i++U)+4VX_|Oa!SdjX%m3h%8j26y3d((S zwRN|bpU5aiX;->`WoXB!pmOSbn#5y!bkeKW5Jc9G2NLAtn)=s=-pI2p+dMOa+Ow!W z)%&rjU8~FaAcxCy=C_I31g%f6w+}4zKl0bVF7z*R6n$!&=ANk3LgVvp)?F|o%9+k7 zFXq9Ql-6{uUtWB*=6R9Lng6xT0klo6G{0|~x|Nlm04_7Pm_G6=uQzICVf8T;2!<4; zG6nUh>5F;^B8H#+}1lrl-`JonJxh&|+}UAAr1y*t-$?AqBr zu54H3iDhwn75!gXKm2>s^sie?&<;}B>mcD4W|R|BLTINKZU1Xm)8T?E{#y>zJx$Hd zGOK;uRdZNlJn=KUwexdTjs?A+sfZd#;jmVEr0m5W*v0TvKQ<3|x_^7Ko%-O2)>rN` zI}D~06mcI(;Xme5f}-Z|kiI;%w2A+2!lsIpB94!T_HiOm`yv*2_eu!J5)>ey%+fy~ z0`-iGz7Xoc3Vk8e9qIZ)sC#|&g-|y}>IJ7LdLW>f5)z5bA4%z7R@L^@UIqS6>L#UO@eHO#=XB s@45nba_!GpxDep`Ad&C@0N^7K{&F5i7AMm_*(Cv!d!O#&iB-z~0|zrasQ>@~ diff --git a/packages/varsig/src/__tests__/canons/jws.test.ts b/packages/varsig/src/__tests__/canons/jws.test.ts index 44d64c8c..63a951c9 100644 --- a/packages/varsig/src/__tests__/canons/jws.test.ts +++ b/packages/varsig/src/__tests__/canons/jws.test.ts @@ -2,6 +2,7 @@ import { fromOriginal, prepareCanonicalization } from '../../canons/jws.js' import { BytesTape } from '../../bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { CanonicalizationKind } from '../../canonicalization.js' +import { MAGIC } from '../../magic.js' //const testJws = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' const testJws = 'eyJhbGciOiAiRVMyNTYifQ.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' @@ -10,7 +11,7 @@ const expectedHashInput = uint8arrays.fromString( testJws.slice(0, testJws.lastIndexOf('.')) ) -test('Encode eip712 message', () => { +test('Encode jws message', () => { // @ts-ignore const node = fromOriginal(testJws) @@ -23,13 +24,15 @@ test('Encode eip712 message', () => { }) }) -test('Canonicalize ipld eip712 object', () => { +test('Canonicalize ipld jws object', () => { // @ts-ignore const node = fromOriginal(testJws) const tape = new BytesTape(node._sig) tape.readVarint() // skip sigil const keyType = tape.readVarint() // skip key type const hashType = tape.readVarint() // skip hash type + expect(keyType).toEqual(MAGIC.ES256) + expect(hashType).toEqual(MAGIC.SHA2_256) tape.readVarint() // skip canonicalizer codec const can = prepareCanonicalization(tape, hashType, keyType) expect(can.kind).toEqual(CanonicalizationKind.JWS) diff --git a/packages/varsig/src/__tests__/jws-es256.test.ts b/packages/varsig/src/__tests__/jws-es256.test.ts index d2d83677..2e5e8dd1 100644 --- a/packages/varsig/src/__tests__/jws-es256.test.ts +++ b/packages/varsig/src/__tests__/jws-es256.test.ts @@ -3,12 +3,12 @@ import { expect, test } from '@jest/globals' import { CARFactory, type CAR } from 'cartonne' import { CID } from 'multiformats/cid' import * as uint8arrays from 'uint8arrays' -// import { JWS } from '../canons/jws.js' +import { JWS } from '../canons/jws.js' import { verify, toOriginal } from '../varsig.js' -// import { MAGIC } from '../magic.js' -// import { BytesTape } from '../bytes-tape.js' +import { MAGIC } from '../magic.js' +import { BytesTape } from '../bytes-tape.js' import { klona } from 'klona' -// import * as varintes from 'varintes' +import * as varintes from 'varintes' const factory = new CARFactory() @@ -27,20 +27,21 @@ describe('jws.car', () => { entries = root.entries as Array }) + test.skip('GenerateVectors', async () => { + await createVectors() + }) + - test.skip('Verify signatures', async () => { + test('Verify signatures', async () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access const node = car.get(entry.node) + console.log(entry) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const verificationKey = - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call - entry.signer.address || - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - uint8arrays.fromString(entry.signer.publicKey.replace(/^0x/, ''), 'hex') + const verificationKey = entry.signer.verificationKey // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (entry.valid) { @@ -103,106 +104,104 @@ describe('jws.car', () => { }) -// import * as jose from 'jose' -// import { generateKeyPairSync } from 'node:crypto' -// import { encode, decode } from '@ipld/dag-json' -// import { pipeline } from "node:stream/promises"; -// import * as fs from "node:fs"; - -// async function createVectors() { -// const car = factory.build() -// const entries = [] -// await gen('ec', { namedCurve: 'secp256k1' }, 'ES256K') -// await gen('ec', { namedCurve: 'P-256' }, 'ES256') -// await gen('ed25519', {}, 'EdDSA') -// await gen('ed25519', {}, 'EdDSA', 'ed25519') -// await gen('ed448', {}, 'EdDSA', 'ed448') -// -// console.log(entries) -// -// car.put({ -// entries, -// canonicalization: 'jws', -// hash: ['sha2-256', 'shake-256'], -// signature: ['es256', 'secp256k1', 'ed25519', 'ed448'] -// }, { isRoot: true }) -// -// await pipeline(car, fs.createWriteStream("./jws.car")); -// -// async function gen(name, opt, alg, crv) { -// const kp = generateKeyPairSync(name, opt) -// const {x, y } = kp.publicKey.export({ format: 'jwk' }) -// const verificationKey = y ? -// uint8arrays.concat([ -// [0x04], -// uint8arrays.fromString(x, 'base64url'), -// uint8arrays.fromString(y, 'base64url') -// ]) : -// uint8arrays.fromString(x, 'base64url') -// -// const payload = JSON.parse(uint8arrays.toString(encode({ testLink: CID.parse('bafyqacnbmrqxgzdgdeaui') }))) -// const jwt = await new jose.SignJWT(payload) -// .setProtectedHeader({ alg }) -// .setIssuedAt() -// .setAudience('urn:example:audience') -// .setExpirationTime('2h') -// .sign(kp.privateKey) -// -// -// const node = JWS.fromOriginal(jwt) -// -// const entry1 = car.put({ -// valid: true, -// signer: { verificationKey }, -// node: car.put(node), -// original: car.put(jwt) -// }) -// const nodeKeccak = klona(node) -// let tape = new BytesTape(nodeKeccak._sig) -// tape.read(1); tape.readVarint(); -// const hashPosition = tape.position -// nodeKeccak._sig.set([MAGIC.KECCAK_256], hashPosition) // TODO - fix -// const entry2 = car.put({ -// valid: false, -// error: 'Invalid hash code', -// signer: { verificationKey }, -// node: car.put(nodeKeccak), -// original: car.put(jwt) -// }) -// const jwtInvalid = jwt.substring(0, jwt.length - 10) + 'abc' + jwt.substring(jwt.length - 7) -// const entry3 = car.put({ -// valid: false, -// error: 'Invalid signature', -// signer: { verificationKey }, -// node: car.put(JWS.fromOriginal(jwtInvalid)), -// original: car.put(jwtInvalid) -// }) -// const invalidProtectedBytes = uint8arrays.fromString(JSON.stringify({})) -// const invalidProtected = uint8arrays.toString(invalidProtectedBytes, 'base64url') -// const jwtMissingAlg = invalidProtected + jwt.substring(jwt.indexOf('.')) -// const nodeMissingAlg = klona(node) -// const protectedLength = varintes.encode(invalidProtectedBytes.length)[0] -// tape = new BytesTape(nodeKeccak._sig) -// tape.read(1); tape.readVarint(); tape.readVarint(); tape.readVarint(); -// const protLenPos = tape.position -// const klonLength = tape.readVarint() -// const signature = nodeMissingAlg._sig.slice(tape.position + klonLength) -// const newVarsig = uint8arrays.concat([ -// nodeMissingAlg._sig.slice(0, protLenPos), -// varintes.encode(invalidProtectedBytes.length)[0], -// invalidProtectedBytes, -// signature -// ]) -// nodeMissingAlg._sig = newVarsig -// const entry4 = car.put({ -// valid: false, -// error: 'Missing alg in protected header', -// signer: { verificationKey }, -// node: car.put(nodeMissingAlg), -// original: car.put(jwtMissingAlg) -// }) -// entries.push(entry1, entry2, entry3, entry4) -// } -// } - - +import * as jose from 'jose' +import { generateKeyPairSync } from 'node:crypto' +import { encode, decode } from '@ipld/dag-json' +import { pipeline } from "node:stream/promises"; +import * as fs from "node:fs"; + +async function createVectors() { + const car = factory.build() + const entries = [] + await gen('ec', { namedCurve: 'P-256' }, 'ES256') + await gen('ed25519', {}, 'EdDSA') + await gen('ed25519', {}, 'EdDSA', 'ed25519') + await gen('ec', { namedCurve: 'secp256k1' }, 'ES256K') + await gen('ed448', {}, 'EdDSA', 'ed448') + + console.log(entries) + + car.put({ + entries, + canonicalization: 'jws', + hash: ['sha2-256', 'shake-256'], + signature: ['es256', 'secp256k1', 'ed25519', 'ed448'] + }, { isRoot: true }) + + await pipeline(car, fs.createWriteStream("./jws.car")); + + async function gen(name, opt, alg, crv) { + const kp = generateKeyPairSync(name, opt) + const {x, y } = kp.publicKey.export({ format: 'jwk' }) + const verificationKey = y ? + uint8arrays.concat([ + [0x04], + uint8arrays.fromString(x, 'base64url'), + uint8arrays.fromString(y, 'base64url') + ]) : + uint8arrays.fromString(x, 'base64url') + + const payload = JSON.parse(uint8arrays.toString(encode({ testLink: CID.parse('bafyqacnbmrqxgzdgdeaui') }))) + const jwt = await new jose.SignJWT(payload) + .setProtectedHeader({ alg }) + .setIssuedAt() + .setAudience('urn:example:audience') + .setExpirationTime('2h') + .sign(kp.privateKey) + + + const node = JWS.fromOriginal(jwt) + + const entry1 = car.put({ + valid: true, + signer: { verificationKey }, + node: car.put(node), + original: car.put(jwt) + }) + const nodeKeccak = klona(node) + let tape = new BytesTape(nodeKeccak._sig) + tape.read(1); tape.readVarint(); + const hashPosition = tape.position + nodeKeccak._sig.set([MAGIC.KECCAK_256], hashPosition) // TODO - fix + const entry2 = car.put({ + valid: false, + error: 'Invalid hash code', + signer: { verificationKey }, + node: car.put(nodeKeccak), + original: car.put(jwt) + }) + const jwtInvalid = jwt.substring(0, jwt.length - 10) + 'abc' + jwt.substring(jwt.length - 7) + const entry3 = car.put({ + valid: false, + error: 'Invalid signature', + signer: { verificationKey }, + node: car.put(JWS.fromOriginal(jwtInvalid)), + original: car.put(jwtInvalid) + }) + const invalidProtectedBytes = uint8arrays.fromString(JSON.stringify({})) + const invalidProtected = uint8arrays.toString(invalidProtectedBytes, 'base64url') + const jwtMissingAlg = invalidProtected + jwt.substring(jwt.indexOf('.')) + const nodeMissingAlg = klona(node) + const protectedLength = varintes.encode(invalidProtectedBytes.length)[0] + tape = new BytesTape(nodeKeccak._sig) + tape.read(1); tape.readVarint(); tape.readVarint(); tape.readVarint(); + const protLenPos = tape.position + const klonLength = tape.readVarint() + const signature = nodeMissingAlg._sig.slice(tape.position + klonLength) + const newVarsig = uint8arrays.concat([ + nodeMissingAlg._sig.slice(0, protLenPos), + varintes.encode(invalidProtectedBytes.length)[0], + invalidProtectedBytes, + signature + ]) + nodeMissingAlg._sig = newVarsig + const entry4 = car.put({ + valid: false, + error: 'Missing alg in protected header', + signer: { verificationKey }, + node: car.put(nodeMissingAlg), + original: car.put(jwtMissingAlg) + }) + entries.push(entry1, entry2, entry3, entry4) + } +} diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 4f4f57f6..11385e5d 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -3,6 +3,7 @@ import { BytesTape } from './bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { UnreacheableCaseError } from './unreachable-case-error.js' import { Eip712 } from './canons/eip712.js' +import { JWS } from './canons/jws.js' import { HashingAlgo } from './hashing.js' import { keccak_256 } from '@noble/hashes/sha3' import type { SigningKind } from './signing.js' @@ -38,6 +39,8 @@ export class CanonicalizationDecoder { read(hashing: HashingAlgo, sigKind: SigningKind): CanonicalizationAlgo { const sigil = this.tape.readVarint() switch (sigil) { + case CanonicalizationKind.JWS: + return JWS.prepareCanonicalization(this.tape, hashing, sigKind) case CanonicalizationKind.EIP712: return Eip712.prepareCanonicalization(this.tape, hashing, sigKind) case CanonicalizationKind.EIP191: { diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index c17fb089..6f5c5bdb 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -2,6 +2,7 @@ import { MAGIC } from './magic.js' import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' import { Secp256k1 } from './signing/secp256k1.js' +import { P256 } from './signing/p256.js' type EthAddress = `0x${string}` type PublicKey = Uint8Array @@ -10,6 +11,7 @@ type VerificationKey = PublicKey | EthAddress export enum SigningKind { RSA = MAGIC.RSA, SECP256K1 = MAGIC.SECP256K1, + P256 = MAGIC.ES256, } export type SigningAlgo = { @@ -36,6 +38,8 @@ export class SigningDecoder { switch (signingSigil) { case SigningKind.SECP256K1: return Secp256k1.prepareVerifier(this.tape) + case SigningKind.P256: + return P256.prepareVerifier(this.tape) case SigningKind.RSA: throw new Error(`Not implemented: signingSigil: RSA`) default: @@ -47,6 +51,8 @@ export class SigningDecoder { switch (signing.kind) { case SigningKind.SECP256K1: return Secp256k1.readSignature(this.tape) + case SigningKind.P256: + return P256.readSignature(this.tape) case SigningKind.RSA: { throw new Error(`Not supported: RSA`) } diff --git a/packages/varsig/src/signing/p256.ts b/packages/varsig/src/signing/p256.ts new file mode 100644 index 00000000..cd1c0c23 --- /dev/null +++ b/packages/varsig/src/signing/p256.ts @@ -0,0 +1,28 @@ +import { MAGIC } from '../magic.js' +import { SigningAlgo } from '../signing.js' +import { p256 } from '@noble/curves/p256' +import * as uint8arrays from 'uint8arrays' +import { keccak_256 } from '@noble/hashes/sha3' +import type { BytesTape } from '../bytes-tape.js' + +const SIGIL = MAGIC.ES256 + +function prepareVerifier(tape: BytesTape): SigningAlgo { + return { + kind: SIGIL, + // eslint-disable-next-line @typescript-eslint/require-await + verify: async (input, signature, verificationKey): Promise => { + // let k1Sig = p256.Signature.fromCompact(signature) + // return p256.verify(signature, input, verificationKey) + console.log(input, signature, verificationKey) + console.log(p256.verify(signature, input, verificationKey)) + return p256.verify(signature, input, verificationKey) + }, + } +} + +function readSignature(tape: BytesTape): Uint8Array { + return tape.read(64) +} + +export const P256 = { SIGIL, prepareVerifier, readSignature } From 074fe3145c1eab04a883f72422f26891974524d0 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Tue, 6 Feb 2024 20:18:22 +0000 Subject: [PATCH 105/110] Implement hashing --- packages/varsig/src/canonicalization.ts | 4 +-- packages/varsig/src/canons/eip712.ts | 4 +-- packages/varsig/src/canons/jws.ts | 9 +++--- packages/varsig/src/hashing.ts | 37 +++++++++++++++++++------ packages/varsig/src/signing/p256.ts | 5 ++-- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 11385e5d..686d87df 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -4,7 +4,7 @@ import * as uint8arrays from 'uint8arrays' import { UnreacheableCaseError } from './unreachable-case-error.js' import { Eip712 } from './canons/eip712.js' import { JWS } from './canons/jws.js' -import { HashingAlgo } from './hashing.js' +import { HashingAlgo, HashingKind } from './hashing.js' import { keccak_256 } from '@noble/hashes/sha3' import type { SigningKind } from './signing.js' @@ -44,7 +44,7 @@ export class CanonicalizationDecoder { case CanonicalizationKind.EIP712: return Eip712.prepareCanonicalization(this.tape, hashing, sigKind) case CanonicalizationKind.EIP191: { - if (hashing !== HashingAlgo.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) + if (hashing.kind !== HashingKind.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) const fn: CanonicalizationEIP191 = (message: string) => { return keccak_256( uint8arrays.fromString( diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 3d887b27..e08f258e 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -163,10 +163,10 @@ const SIGIL = MAGIC.EIP712 export function prepareCanonicalization( tape: BytesTape, - hashType: HashingAlgo, + hashing: HashingAlgo, keyType: SigningKind ): CanonicalizationAlgo { - if (hashType !== SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashType}`) + if (hashing.kind !== SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashing}`) if (!SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) const metadataLength = tape.readVarint() const metadataBytes = tape.read(metadataLength) diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index aea05143..abd94ca8 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -31,7 +31,7 @@ export const JWS = { SIGIL, prepareCanonicalization, fromOriginal } export function prepareCanonicalization( tape: BytesTape, - hashType: HashingAlgo, + hashing: HashingAlgo, keyType: SigningKind ): CanonicalizationAlgo { const protectedLength = tape.readVarint() @@ -43,14 +43,15 @@ export function prepareCanonicalization( const keyTypeFromProtected = findKeyType(protected1) if (keyType !== keyTypeFromProtected) throw new Error(`Key type missmatch: ${keyType}, ${keyTypeFromProtected}`) - if (hashType !== HASH_BY_KEY_TYPE[keyType]) - throw new Error(`Hash type missmatch: ${hashType}, ${HASH_BY_KEY_TYPE[keyType]}`) + if (hashing.kind !== HASH_BY_KEY_TYPE[keyType]) + throw new Error(`Hash type missmatch: ${hashing.kind}, ${HASH_BY_KEY_TYPE[keyType]}`) const can = (node: IpldNode) => { // encode node using dag-json from multiformats const payloadB64u = toB64u(encode(node)) const protectedB64u = toB64u(protectedBytes) - return uint8arrays.fromString(`${protectedB64u}.${payloadB64u}`) + const input = uint8arrays.fromString(`${protectedB64u}.${payloadB64u}`) + return hashing.digest(input) } can.kind = SIGIL can.original = (node: IpldNode, signature: Uint8Array) => { diff --git a/packages/varsig/src/hashing.ts b/packages/varsig/src/hashing.ts index d11706e8..795a72a9 100644 --- a/packages/varsig/src/hashing.ts +++ b/packages/varsig/src/hashing.ts @@ -1,13 +1,25 @@ import { MAGIC } from './magic.js' import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' +import { sha256 } from '@noble/hashes/sha256' +import { sha512 } from '@noble/hashes/sha512' +import { keccak_256 } from '@noble/hashes/sha3' -export enum HashingAlgo { +export enum HashingKind { SHA2_256 = MAGIC.SHA2_256, SHA2_512 = MAGIC.SHA2_512, KECCAK256 = MAGIC.KECCAK_256, } +export type HashFn = ( + input: Uint8Array +) => Uint8Array + +export type HashingAlgo = { + kind: HashingKind + digest: HashFn +} + export class HashingDecoder { constructor(private readonly tape: BytesTape) {} @@ -16,14 +28,23 @@ export class HashingDecoder { } read(): HashingAlgo { - const hashingSigil = this.tape.readVarint() + const hashingSigil = this.tape.readVarint() switch (hashingSigil) { - case HashingAlgo.SHA2_512: - return HashingAlgo.SHA2_512 - case HashingAlgo.SHA2_256: - return HashingAlgo.SHA2_256 - case HashingAlgo.KECCAK256: - return HashingAlgo.KECCAK256 + case HashingKind.SHA2_512: + return { + kind: HashingKind.SHA2_512, + digest: sha512 + } + case HashingKind.SHA2_256: + return { + kind: HashingKind.SHA2_256, + digest: sha256 + } + case HashingKind.KECCAK256: + return { + kind: HashingKind.KECCAK256, + digest: keccak_256 + } default: throw new UnreacheableCaseError(hashingSigil, 'hashing algo') } diff --git a/packages/varsig/src/signing/p256.ts b/packages/varsig/src/signing/p256.ts index cd1c0c23..80090492 100644 --- a/packages/varsig/src/signing/p256.ts +++ b/packages/varsig/src/signing/p256.ts @@ -1,12 +1,11 @@ import { MAGIC } from '../magic.js' import { SigningAlgo } from '../signing.js' import { p256 } from '@noble/curves/p256' -import * as uint8arrays from 'uint8arrays' -import { keccak_256 } from '@noble/hashes/sha3' import type { BytesTape } from '../bytes-tape.js' const SIGIL = MAGIC.ES256 +// @ts-ignore function prepareVerifier(tape: BytesTape): SigningAlgo { return { kind: SIGIL, @@ -14,7 +13,7 @@ function prepareVerifier(tape: BytesTape): SigningAlgo { verify: async (input, signature, verificationKey): Promise => { // let k1Sig = p256.Signature.fromCompact(signature) // return p256.verify(signature, input, verificationKey) - console.log(input, signature, verificationKey) + console.log(input, signature, verificationKey.slice(1)) console.log(p256.verify(signature, input, verificationKey)) return p256.verify(signature, input, verificationKey) }, From 96c92710444f7f27213c50e1e104c48e4dc31904 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 8 Feb 2024 14:55:57 +0000 Subject: [PATCH 106/110] Fix jws canonicalization + old tests --- .../varsig/src/__tests__/canons/eip712.test.ts | 5 +++-- .../varsig/src/__tests__/canons/jws.test.ts | 16 ++++++++++++---- .../varsig/src/__tests__/jws-es256.test.ts | 18 ++++++++++++------ packages/varsig/src/canons/jws.ts | 3 +++ packages/varsig/src/varsig.ts | 1 + 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/packages/varsig/src/__tests__/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts index edbca0ee..3007856a 100644 --- a/packages/varsig/src/__tests__/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -1,6 +1,7 @@ import { fromOriginal, prepareCanonicalization } from '../../canons/eip712.js' import { BytesTape } from '../../bytes-tape.js' import * as uint8arrays from 'uint8arrays' +import { HashingDecoder } from '../../hashing.js' import { CanonicalizationKind } from '../../canonicalization.js' const testData = { @@ -65,9 +66,9 @@ test('Canonicalize ipld eip712 object', () => { tape.readVarint() // skip sigil tape.readVarint() // skip key type tape.read(1) // skip recovery bit - tape.readVarint() // skip hash type + const hashing = HashingDecoder.read(tape) // read hash type tape.readVarint() // skip canonicalizer codec - const can = prepareCanonicalization(tape, 0x1b, 0xe7) + const can = prepareCanonicalization(tape, hashing, 0xe7) if (can.kind !== CanonicalizationKind.EIP712) throw new Error(`Nope`) expect(tape.remainder.length).toEqual(64) // @ts-ignore diff --git a/packages/varsig/src/__tests__/canons/jws.test.ts b/packages/varsig/src/__tests__/canons/jws.test.ts index 63a951c9..2737876c 100644 --- a/packages/varsig/src/__tests__/canons/jws.test.ts +++ b/packages/varsig/src/__tests__/canons/jws.test.ts @@ -2,11 +2,13 @@ import { fromOriginal, prepareCanonicalization } from '../../canons/jws.js' import { BytesTape } from '../../bytes-tape.js' import * as uint8arrays from 'uint8arrays' import { CanonicalizationKind } from '../../canonicalization.js' +import { HashingDecoder } from '../../hashing.js' import { MAGIC } from '../../magic.js' //const testJws = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' const testJws = 'eyJhbGciOiAiRVMyNTYifQ.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + const expectedHashInput = uint8arrays.fromString( testJws.slice(0, testJws.lastIndexOf('.')) ) @@ -21,7 +23,12 @@ test('Encode jws message', () => { sub: '1234567890', name: 'John Doe', iat: 1516239022 + }) }) + +test('Encode jws with unordered fields fails', () => { + const unorderedJws = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + expect(() => fromOriginal(unorderedJws)).toThrowError(/Invalid JOSE payload: Varsig only supports JSON with ordered keys, got/) }) test('Canonicalize ipld jws object', () => { @@ -30,15 +37,16 @@ test('Canonicalize ipld jws object', () => { const tape = new BytesTape(node._sig) tape.readVarint() // skip sigil const keyType = tape.readVarint() // skip key type - const hashType = tape.readVarint() // skip hash type + const hashing = HashingDecoder.read(tape) // read hash type + // const hashType = tape.readVarint() // skip hash type expect(keyType).toEqual(MAGIC.ES256) - expect(hashType).toEqual(MAGIC.SHA2_256) + expect(hashing.kind).toEqual(MAGIC.SHA2_256) tape.readVarint() // skip canonicalizer codec - const can = prepareCanonicalization(tape, hashType, keyType) + const can = prepareCanonicalization(tape, hashing, keyType) expect(can.kind).toEqual(CanonicalizationKind.JWS) expect(tape.remainder.length).toEqual(32) // @ts-ignore delete node._sig const sigInput = can(node) - expect(sigInput).toEqual(expectedHashInput) + expect(sigInput).toEqual(hashing.digest(expectedHashInput)) }) diff --git a/packages/varsig/src/__tests__/jws-es256.test.ts b/packages/varsig/src/__tests__/jws-es256.test.ts index 2e5e8dd1..d1e91ed6 100644 --- a/packages/varsig/src/__tests__/jws-es256.test.ts +++ b/packages/varsig/src/__tests__/jws-es256.test.ts @@ -27,12 +27,12 @@ describe('jws.car', () => { entries = root.entries as Array }) - test.skip('GenerateVectors', async () => { + test('GenerateVectors', async () => { await createVectors() }) - test('Verify signatures', async () => { + test.skip('Verify signatures', async () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) @@ -141,17 +141,23 @@ async function createVectors() { ]) : uint8arrays.fromString(x, 'base64url') - const payload = JSON.parse(uint8arrays.toString(encode({ testLink: CID.parse('bafyqacnbmrqxgzdgdeaui') }))) + const payload = JSON.parse(uint8arrays.toString(encode({ + testLink: CID.parse('bafyqacnbmrqxgzdgdeaui'), + iat:1707403055, + aud:"urn:example:audience", + exp:1707410255 + }))) const jwt = await new jose.SignJWT(payload) .setProtectedHeader({ alg }) - .setIssuedAt() - .setAudience('urn:example:audience') - .setExpirationTime('2h') .sign(kp.privateKey) + console.log('test: jwt', jwt) const node = JWS.fromOriginal(jwt) + await expect(verify(node, verificationKey)).resolves.toEqual(true) + console.log('passed one') + const entry1 = car.put({ valid: true, signer: { verificationKey }, diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index abd94ca8..5b7b450e 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -66,6 +66,9 @@ export function prepareCanonicalization( export function fromOriginal(jws: string): IpldNodeSigned { const [protectedB64u, payloadB64u, signatureB64u] = jws.split('.') const node = decode>(fromB64u(payloadB64u)) + if (toB64u(encode(node)) !== payloadB64u) { + throw new Error(`Invalid JOSE payload: Varsig only supports JSON with ordered keys, got "${JSON.stringify(node)}"`) + } const protectedBytes = fromB64u(protectedB64u) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const protected1 = JSON.parse(uint8arrays.toString(protectedBytes)) diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 14bb4865..ab156453 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -26,6 +26,7 @@ export async function verify( const toCanonicalize = klona(node) // @ts-ignore delete toCanonicalize._sig + console.log('vars: jwt', canonicalization.original(toCanonicalize, signature)) // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const signatureInput = canonicalization(toCanonicalize) From f5ffa2003e6b7f0dfdabfe1f655f552056e90111 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 8 Feb 2024 19:36:07 +0000 Subject: [PATCH 107/110] JWS works for p256 and secpk1 --- .../src/__tests__/canonicalization.test.ts | 6 +- packages/varsig/src/__tests__/hashing.test.ts | 8 +-- .../varsig/src/__tests__/jws-es256.test.ts | 11 ++-- packages/varsig/src/canons/jws.ts | 1 + packages/varsig/src/hashing.ts | 61 +++++++++++++------ packages/varsig/src/signing/p256.ts | 4 -- packages/varsig/src/signing/secp256k1.ts | 5 +- packages/varsig/src/varsig.ts | 1 - 8 files changed, 59 insertions(+), 38 deletions(-) diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index 9398eca8..b5f861a2 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -3,7 +3,7 @@ import { BytesTape } from '../bytes-tape.js' import { CanonicalizationDecoder, CanonicalizationKind } from '../canonicalization.js' import { concat, toString } from 'uint8arrays' import { encode } from 'varintes/encode' -import { HashingAlgo } from '../hashing.js' +import { HashingKind, hashingAlgoByKind } from '../hashing.js' import { SigningKind } from '../signing.js' import { fromOriginal } from '../canons/eip712.js' @@ -95,7 +95,7 @@ test('EIP712', () => { tape.readVarint() // skip hash sigil const canonicalization = CanonicalizationDecoder.read( tape, - HashingAlgo.KECCAK256, + hashingAlgoByKind(HashingKind.KECCAK256), SigningKind.SECP256K1 ) expect(canonicalization.kind).toEqual(CanonicalizationKind.EIP712) @@ -107,7 +107,7 @@ test('EIP712', () => { test('EIP191', () => { const bytes = concat([encode(0xe191)[0]]) const tape = new BytesTape(bytes) - const result = CanonicalizationDecoder.read(tape, HashingAlgo.KECCAK256, SigningKind.SECP256K1) + const result = CanonicalizationDecoder.read(tape, hashingAlgoByKind(HashingKind.KECCAK256), SigningKind.SECP256K1) expect(result.kind).toEqual(CanonicalizationKind.EIP191) const canonicalized = toString(result('Hello'), 'hex') expect(canonicalized).toEqual('aa744ba2ca576ec62ca0045eca00ad3917fdf7ffa34fbbae50828a5a69c1580e') diff --git a/packages/varsig/src/__tests__/hashing.test.ts b/packages/varsig/src/__tests__/hashing.test.ts index 3da3154a..80cc68dc 100644 --- a/packages/varsig/src/__tests__/hashing.test.ts +++ b/packages/varsig/src/__tests__/hashing.test.ts @@ -1,22 +1,22 @@ import { expect, test } from '@jest/globals' import { hex } from './hex.util.js' -import { HashingAlgo, HashingDecoder } from '../hashing.js' +import { HashingKind, HashingDecoder } from '../hashing.js' import { BytesTape } from '../bytes-tape.js' test('SHA2_256', () => { const tape = new BytesTape(hex(0x12)) const result = HashingDecoder.read(tape) - expect(result).toEqual(HashingAlgo.SHA2_256) + expect(result.kind).toEqual(HashingKind.SHA2_256) }) test('SHA2_512', () => { const tape = new BytesTape(hex(0x13)) const result = HashingDecoder.read(tape) - expect(result).toEqual(HashingAlgo.SHA2_512) + expect(result.kind).toEqual(HashingKind.SHA2_512) }) test('KECCAK256', () => { const tape = new BytesTape(hex(0x1b)) const result = HashingDecoder.read(tape) - expect(result).toEqual(HashingAlgo.KECCAK256) + expect(result.kind).toEqual(HashingKind.KECCAK256) }) diff --git a/packages/varsig/src/__tests__/jws-es256.test.ts b/packages/varsig/src/__tests__/jws-es256.test.ts index d1e91ed6..1a63e575 100644 --- a/packages/varsig/src/__tests__/jws-es256.test.ts +++ b/packages/varsig/src/__tests__/jws-es256.test.ts @@ -38,7 +38,6 @@ describe('jws.car', () => { const entry = car.get(entryCID) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access const node = car.get(entry.node) - console.log(entry) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const verificationKey = entry.signer.verificationKey @@ -114,10 +113,10 @@ async function createVectors() { const car = factory.build() const entries = [] await gen('ec', { namedCurve: 'P-256' }, 'ES256') - await gen('ed25519', {}, 'EdDSA') - await gen('ed25519', {}, 'EdDSA', 'ed25519') await gen('ec', { namedCurve: 'secp256k1' }, 'ES256K') - await gen('ed448', {}, 'EdDSA', 'ed448') + await gen('ed25519', {}, 'EdDSA') + // await gen('ed25519', {}, 'EdDSA', 'ed25519') + // await gen('ed448', {}, 'EdDSA', 'ed448') console.log(entries) @@ -145,14 +144,14 @@ async function createVectors() { testLink: CID.parse('bafyqacnbmrqxgzdgdeaui'), iat:1707403055, aud:"urn:example:audience", - exp:1707410255 + // exp:1707410255 }))) const jwt = await new jose.SignJWT(payload) .setProtectedHeader({ alg }) .sign(kp.privateKey) - console.log('test: jwt', jwt) + const node = JWS.fromOriginal(jwt) await expect(verify(node, verificationKey)).resolves.toEqual(true) diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index 5b7b450e..43242728 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -83,6 +83,7 @@ export function fromOriginal(jws: string): IpldNodeSigned { const varsig = uint8arrays.concat([ new Uint8Array([MAGIC.VARSIG]), // varsig sigil varintes.encode(keyType)[0], // key type + ...(keyType === MAGIC.SECP256K1 ? [new Uint8Array([0])] : []), // recovery bit not included (only for secp256k1) varintes.encode(hashType)[0], // hash type varintes.encode(SIGIL)[0], // canonicalizer codec protectedLength, diff --git a/packages/varsig/src/hashing.ts b/packages/varsig/src/hashing.ts index 795a72a9..3fa031b4 100644 --- a/packages/varsig/src/hashing.ts +++ b/packages/varsig/src/hashing.ts @@ -20,6 +20,28 @@ export type HashingAlgo = { digest: HashFn } +export function hashingAlgoByKind(kind: HashingKind): HashingAlgo { + switch (kind) { + case HashingKind.SHA2_512: + return { + kind: HashingKind.SHA2_512, + digest: sha512 + } + case HashingKind.SHA2_256: + return { + kind: HashingKind.SHA2_256, + digest: sha256 + } + case HashingKind.KECCAK256: + return { + kind: HashingKind.KECCAK256, + digest: keccak_256 + } + default: + throw new UnreacheableCaseError(kind, 'hashing algo') + } +} + export class HashingDecoder { constructor(private readonly tape: BytesTape) {} @@ -29,24 +51,25 @@ export class HashingDecoder { read(): HashingAlgo { const hashingSigil = this.tape.readVarint() - switch (hashingSigil) { - case HashingKind.SHA2_512: - return { - kind: HashingKind.SHA2_512, - digest: sha512 - } - case HashingKind.SHA2_256: - return { - kind: HashingKind.SHA2_256, - digest: sha256 - } - case HashingKind.KECCAK256: - return { - kind: HashingKind.KECCAK256, - digest: keccak_256 - } - default: - throw new UnreacheableCaseError(hashingSigil, 'hashing algo') - } + return hashingAlgoByKind(hashingSigil) + // switch (hashingSigil) { + // case HashingKind.SHA2_512: + // return { + // kind: HashingKind.SHA2_512, + // digest: sha512 + // } + // case HashingKind.SHA2_256: + // return { + // kind: HashingKind.SHA2_256, + // digest: sha256 + // } + // case HashingKind.KECCAK256: + // return { + // kind: HashingKind.KECCAK256, + // digest: keccak_256 + // } + // default: + // throw new UnreacheableCaseError(hashingSigil, 'hashing algo') + // } } } diff --git a/packages/varsig/src/signing/p256.ts b/packages/varsig/src/signing/p256.ts index 80090492..192cbfc9 100644 --- a/packages/varsig/src/signing/p256.ts +++ b/packages/varsig/src/signing/p256.ts @@ -11,10 +11,6 @@ function prepareVerifier(tape: BytesTape): SigningAlgo { kind: SIGIL, // eslint-disable-next-line @typescript-eslint/require-await verify: async (input, signature, verificationKey): Promise => { - // let k1Sig = p256.Signature.fromCompact(signature) - // return p256.verify(signature, input, verificationKey) - console.log(input, signature, verificationKey.slice(1)) - console.log(p256.verify(signature, input, verificationKey)) return p256.verify(signature, input, verificationKey) }, } diff --git a/packages/varsig/src/signing/secp256k1.ts b/packages/varsig/src/signing/secp256k1.ts index 4a213bbf..4135e674 100644 --- a/packages/varsig/src/signing/secp256k1.ts +++ b/packages/varsig/src/signing/secp256k1.ts @@ -34,7 +34,10 @@ function prepareVerifier(tape: BytesTape): SigningAlgo { } return false } else { - return secp256k1.verify(signature, input, verificationKey) + // In Bitcoin only low S is allowed to prevent problems with malleability + // However, outside of bitcoin low S is not enforced when creating signatures + // so we need to be able to validate both low and non-low S values. + return secp256k1.verify(signature, input, verificationKey, { lowS: false }) } }, } diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index ab156453..14bb4865 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -26,7 +26,6 @@ export async function verify( const toCanonicalize = klona(node) // @ts-ignore delete toCanonicalize._sig - console.log('vars: jwt', canonicalization.original(toCanonicalize, signature)) // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const signatureInput = canonicalization(toCanonicalize) From bd0978e1c742fb0ecbb3f387b13573374e257480 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 8 Feb 2024 21:04:39 +0000 Subject: [PATCH 108/110] Most things are working --- packages/varsig/package.json | 6 +- .../varsig/src/__tests__/__vectors__/jws.car | Bin 14461 -> 10776 bytes .../src/__tests__/canonicalization.test.ts | 8 +- .../src/__tests__/canons/eip712.test.ts | 2 +- .../varsig/src/__tests__/canons/jws.test.ts | 19 +- .../src/__tests__/eip712-secp256k1.test.ts | 2 +- packages/varsig/src/__tests__/gen-vectors.ts | 22 +- .../varsig/src/__tests__/jws-es256.test.ts | 287 +++++++++--------- .../varsig/src/__tests__/parse-eip191.test.ts | 10 +- .../varsig/src/__tests__/parse-eip712.test.ts | 2 +- packages/varsig/src/bytes.ts | 2 +- packages/varsig/src/canonicalization.ts | 20 +- packages/varsig/src/canons/eip712.ts | 10 +- packages/varsig/src/canons/jws.ts | 12 +- packages/varsig/src/hashing.ts | 10 +- packages/varsig/src/signing.ts | 8 +- packages/varsig/src/signing/ed25519.ts | 23 ++ packages/varsig/src/varsig.ts | 5 +- pnpm-lock.yaml | 197 ++++++------ 19 files changed, 360 insertions(+), 285 deletions(-) create mode 100644 packages/varsig/src/signing/ed25519.ts diff --git a/packages/varsig/package.json b/packages/varsig/package.json index edf39554..7543a840 100644 --- a/packages/varsig/package.json +++ b/packages/varsig/package.json @@ -47,7 +47,7 @@ "homepage": "https://github.com/ceramicnetwork/js-did#readme", "devDependencies": { "@stablelib/random": "^1.0.2", - "cartonne": "^2.2.0", + "cartonne": "^3.0.1", "tsm": "^2.3.0" }, "dependencies": { @@ -60,8 +60,8 @@ "key-did-provider-ed25519": "workspace:^", "key-did-resolver": "workspace:^", "klona": "^2.0.6", - "multiformats": "^11.0.2", - "uint8arrays": "^4.0.3", + "multiformats": "^13.0.1", + "uint8arrays": "^5.0.2", "varintes": "^2.0.5", "viem": "^1.19.5" } diff --git a/packages/varsig/src/__tests__/__vectors__/jws.car b/packages/varsig/src/__tests__/__vectors__/jws.car index 6d3e9b9170890ae64d004757305b153686f56227..ddde7a8197a79ea1fd525f5e94f3ee8222c2212f 100644 GIT binary patch literal 10776 zcmds-c~Dd58ixg`yQM|h1Z7dIsGwm91YF7zLI@;=EemMOa*`afl7xf=87&AZE?f}` zcvZj>5i5&&5%j94EG~$E8wd(&tx-WyK`khH1939fw4DPTsU81Ha^{^(zWF_Q-{*Yi z^VkcB1OkcphnD$7yD@svM%I(oAh&zZs%h%r%25V46VYDc$bnx~?Be2tRPQHN?blgS zKqLkQe7#D&!E$!T)Z*FpMgOibxZ;|75H(X-^;>0Q>!w=!C8ui6C^F~e)2T5GNsNf^ z0mM>yLN4H;{tN( z@2&;Q3-%=Ee_O*^9hCiBQZ^-H}z0##>uE#%6 z9l_@C4O;?;C4nHH(=s2cUf`ICc?_zU!8F(N-K0Obfb8J#S_jg&NmGQl;99D)|3d0aNb2MrGjrOU7iyevrJ1;d8OKn5yY!9d0e zX*@BA{v>83=N*S)RAJQR0yb^SSXn*T;CXQZOkpEYj9w;<)HEl;us2 z=_&aPzJLK}Nr8$UkW#rI<0Vs_E*}u(3Dn?#Ops2MsOiA~a-x?(d=lIB_UYbR!ot*M z{mSF1R;zQyWk1`f|31~esMh3SS+x>{&dzL-c)aW{QuseAzt~am+Q_omB)}v;`Q-`k zrEGx+WPyAtSNofw($qj};$8~dPdYh^vgdVkoCkY1v4#1lvN!$Y-t_AFX=~0&Mg*NT5{UvS%@(&!)B zjt1t}G|v0Jngd6VZ)EMPbJ-KR@nvh(!xg89GemyfNAw-f=)epWy$;NS-G{0kK%WI> zsFV*6{hLo8eB0Ci-Hz@wWX7g~X*I=|H`&-}HoLxCwP#Dz{C4mDVZ$8oVTS6*jpEq+ zZRj`amu4Cx63F;Ht8MoEa7J8PzRQ*#NOMj*rsPier8QR+dj)V_{Dn=)Z9~Fv<6JBy z?_%7o@rz?9tp3_l1Be;(!t_Igws~}J;2Kg*&wftCvd~2ad zAOYwSfMLxBs0={#Ia)DP#%G2|nAg~Q(y8oFSyAWvbWX@on*pc!b&XR(96kG$VRibl zRl}y2T3oX-ov$N$P-=3EZ7XiWn|C5J&Z+ zNW262q0Ep_1rY;C1NkDVzZVl5h;fMa;qXWx0}b}+{p`Yq7w(YyO-!I-_$09{9O3 z%)`ts93Ng6(r`T3hR6M{DpSA39~-kP@|a{+Qf!3Ds}W`RiJK2tzHm6rCri{zXP~#( z6=zl?xW?iOB5m>+r?_jaPg|~9Z)Pze>5FbYY8U*_d^A9xRJK3BnRo7pnR?SAt%qs< zXkT-2cel9yp{-3{#LoC*IzU6kuLJZ5T=3Ha4VANCa5sX+KjhC3@Fg#^2^-&?XVSOg zWj!x@o;2F?BIvPy_nM2t<=KB|ma0AQ&qtre<90HUx3;KoPH%e9Yrl0aom*X<2gXhF zv7?4Y6Km2Q&icF_H#&1p<8c(9p5pT>Gvlrw$v=&IRbjELf!h52xH?ep?g?aiy}iE< zYEa31GLQT8(|w_ zRP$aJX5G^ZetWHCE3n||(X4ZsE#d*aoC$x9vSugGTye_^*0&qgxL^5O{~U|Rx2~?u zhN{&U-qCqR)iZ@{AKy}wk-?$(kk7c2M+sv4xr0Iu2kz$QlkB8>9qBG?KF9>w(W zsvXMm-Gt+t+Rl={xllUx%2}K8=c|J2rTK8X3Y)lvD^n9ii8>Ak6+M*0SszHZGnnY+ zGRbq_(XXTC{j4-=%vtcT*E7NL5glP%WN`NPiUt*yhRI!H2F@z489f;&a||q+i*BfE zIRN8#f$hd`r(c;@x-oe~On$skH`+|L4+XJlIp#=H=i!yDGmGjnRB4YkT`c*x`L~T< zB~LU8?!ACU=rkFq_3F&CVGG)*1+=fOvg4_&Y4S^>X~~Gami73|=4tZiYZH|Q-&Z6) z+eH{rK%cZ}faT7^X&RZ;gfnT4g@?Zm4Sn?!te%%LC80fgX=GpV6Utn-$1X>{;HIH= zuMbT_!|Dv%F&_JG-C+Qy&AolrqMHa z^{?j{3AK}y6?Sk|#f+|4vo5<;wNJJmH{7w;TwZeO^Xl2?jIbv|owM~4Tt92-H@9yc zpRRZ9=%c$EZksw=zqr+t*~WQTm0zLKp%E(FPo!sm0Zyn0HIwdn7$TsRbNj{1Zk40> zj3@0xf<^FT&!<+z18u$=Ea%=IHk^NEU(T18&fn?9%gsvR$y{H2JqPKfq(o-7+BbXZ z9h_A;-^4N@BIf3}0k`~>K79($+f8>8x6O!NYqj4*0YH=KfDljtn21m=XNA+YdZ2A;x&;}FilE@&JM#rLE5!&wA6hbF+&cv3H! z#=bV!NxMpW(+{k>+KX-`C7tK>gV%ksQ~!hSJM8m&`+^Ef!({on`}eG7rVy7bYmyqK>m{=FWQ~Wo8;OkG9(`T(J4t+k+9M-`n>DW(oq{EuVG0y6lM#t5DJFusVGE8ab;_A!_W) z0=Ou<(9(qdc7;cZ0-lT?Hmm<Y|%73$DiK4Y-iC$PnAs*{8ISQ@B) z!vO~0u>ihA1OnoJX^$X5oubkfLLK7K7D646&=x{Hn${LVJ>%0BLOo#87D8>@YYU;a zUA2W!dsx~+s67^KA=C}7wh-#xLt6-CdfGxL6VVnzjR9>T)ISu|w*2(~ARb~CpbOQn l92g5=IJvsQ5CFh%ad8*YseA!neJ+m+Du&MH(W7MI{{f;Wv}ynV literal 14461 zcmeI3c~nzpw}(Lx<#S|Ei%>v{pdbh_OoCzvBqSsOLLdPL2+1KiNl3^@0C57NC?K{n zIH6E+LO?4+t>TCxgQ6gaSQV8P1qaF?s90|#uH4VGT?bgwdROmXgsi<5ocx}5?zl18bJf)Ox~5SwL$4Vnn`KoF8xIC$GE27rb`39tP5iFI4np+wMjAr` z2!tSytA17eL;2O}jHUY!yEZ#anUCA#d$lnL9_BhJZOgXi^hK-d9=v~>N27}ABC&wG z7?4sUqd34~`R^dWr2#ZR8Z{fZxb+APq>AJ(cuDk7A;_SR;p@zG8(P(;#hFn#470^% zUI8!%N3-}TwU1k`N!IHfVzrKs?_Xd$Rcrn%bZV>Y;qrNxCwFSYUOYJQ8!vuExwOt+ z&l^@AmRXT={r<_tobgVA?rpbLonndrp$G?Z+09l2`IV|^=g_G_dW3#+L0^{FBD^OphT!QHOCaHem`Id;2omFn;ReNov*aHb8YQBkr5xW6@1r@K@JIp& z14e@(6lS#i???%;h(&moKb$}cjE(TO^`ynJ;COo&gXqhpiV>)Y2$~SbfTMkgbZ?dz ziG~LWc~bva0g{9hFLLshhA*}9#=&il!4~OzLrV8H5Yq!PV=3*Y2l+w2k2ZL9Z-@4g$5v@azJ`t zggkUEAjsj#!2%;d8dW5x%Lj;|AT+gnUZ*0ni*l_S%=&>VbMJU1FXOHiXH}c9n|f;> zC1Z_T3plABsW%^N(49am)I)W;mK-c8S-Pc^u;n;F3){ow2|xzOrE--26I93wsBL!! zv%gQvxScCQ7VymcZ8QQR;a}L`^qpQ5~Qt#-XHvC(mem@jhovxsj)MZNk%gRTfVL-5ut;&ONjBaUEThpS3@MWw7D&CsS`OTrIm` z!%@<^)kne-cBd3S%6saz>y*I#M%ivhqr`t-f1-xdKK3UC?3GD3TaI2Lv2k}d{<00p z$DX4wrm4+hXl_P@&b{Ae)~*6O6dEe9e`r%w`6T;CV229-^w4Vmu_xI0M7vIr&ynV( z&#mCs;_CBg&Gmic?e}hV_aW&6BZnRPaTt$_8^ig#ZD{@;zDOxE+O_P$X<0(m{@{3@ z@VM90O9efmf~w5T2LeL0rB8uHcK?oXn>zG?Xw}cu+mYP5fNgX&wPvGs)ETaz&@ET| z(7Y+beByxH0F@31{)tu&6}@6A z56%cKlXlFwa>1XLYx;Ehv~`r_r8-78ZdSDXCkN5K=E%tDCc~epf7;6Fo&gB6__mC- zCts1L_q+(0ve@q_X8(!y)wbWX{yzR(1xf3;2o*FD<9u^hFq*ivvTmw|4LdARoQxJ< z)xR0x+S~57895nBHK$);?vHyNWcqHLYoBm=&Y>OA`Qzh^x?fl}yCaA-eFr~nFJ5MGSta#1^kN5J~TBAmZ8nw zAxlWtjWlgzw-&kuh!*a3M_t!aQB5c`pj6Y;c01=nR^eqQkDn$leECakuAqE3TdjUI zc^5w|Pyd#+<{wQGDnkXc4lBg?z2%rAb1rGXoy}48_ov;-INJD1$mLkN?3pi4aev|n8n z88#eFA?UFQtIL6V7U;Hz-*n`yQehbi4HcG$EsCRH87fp%T zzbBPQRcM6@|1){uDBy)kRxvVPJposB-R*HU!MGt^ZB1Ni0cRX($!u})eDl*rUl+_8 zIlO^ny$;)(m^nSz4%^oF{-}yAo;oY_`TeEZ0 zOL}pXSa>U^Umd;y-mx9&9@IM#O7m7xrp*e=oa6Vjp=`X7$V{?4Kw~0VQq&y$aK|IP z%u@3IjYf=)l{kqh6htV&j_g6=JK0GiC{$GJA{184bo3!p{Csht3?>!hFQ9m{?Ib}M zawr8KPPd8lmqD=Oh!ZYh4 zjprSg<;*`^SE^lYN=TWZwNpj&pwNKSyx-qeXf|d<{0t-+;%R2(CJXf)?{w!HkK1C3 z$$Ly@ng3DqpfXf2+oHL|R@c~?*kQSLhyOw9;T@LFo>uA7n=M&;etWzu%58Ma{+Z^D z9kYtj_`tF@9`kPP;EGLcOQ$Zs7nq%beZ1zn$cScE_FU&-$6b|X19jmlqjC6}H&#h? zs4=R7*8UKWe10%Q-y)yA^Ca$0Hu)hjd$Z_JSeCi^=#u&~%^P)6V422ny32WOKr|B* zye8wtuJfKtP4Iko>mbZ^e85s(yvx^Dq6GiG?jqN`kKIMV@-MY-iTkEy)Hk<{8-G~; zn0sVddnhsAWE{r7KvuHSAX|lHC^S@99=_&{oMotN6`GeJ>8zU+YEcI(KAFxLu$q4> zI)9D6ExZ4$%!=V0vF60cS^h808#TU=!%?Am>Xwe~twplj%sjX6k7xIrNS5%{7c^yy zLZVuHx}F)Qe36JV;Q%A6}!m0dS7#V=3jY#G2fb+RYTB&*O0 z75?Yayph8Tm2B0J)iy7fIxUmjZ7e z+RdB($-%LYS^NfRVr}KmbALLUvOZRF%u*ITzO`NFF3o)4sAgQa#8@*(?YLglDOs=O z%1J-f_9b3Oe)0fmyFMDz(c5z0_!*|;uRSk!XruP@ueGqH{OJ2%X>9aMXog#M>+}cF2N)ZmM<&9KgQ96ZpY=j5Cnj?Sc(;W zJUeDxvW?Tz<%^#rq+jM~_7n)-vWi_h-`u$TV$sdlSFTy9m>v`zP^O2_Y!vbZu0N>Y z-^jJ%Jk9sznwlDE-{hO?FLXYs(fwuIvc_Ot%Jr3YhDXa%OY729Iug{CtIW?~yWl^U zrMCBM`d6femVMSex&c)MZK+PBlfN<6rnWU&B{XpjejiQ05(Zb;f?J7P2BQ&fUq4 zU;0xL+gkdrsxZIAsIAnb?ZwD>$9|m2@_oaxo5BjA47=@OhpV1`Uw7f9oC?<`1I#U# z-`$UQsV-PhnPRpvCfH!Hu7F97abo7#dnzSp^<7^1;3Rv4uFR*>MOz?fJ&)J1@?v~H6 zxQ^^=>`JcwPx=j)(Dm!|I`-l*aCPt^DfrR)&bHa`Yr2m7<)xL|&s}Z-f6qLi{_Xvc zf_sHsh?nWQm-X5yS?4lObiH0_p!U~_`gllD&#E0r*l!foys{?6RZ?GUjG&*AWj1HT zd(7IYb`dZ(UlKzG$X-NWYzR&4FJ*c-;R2mp1Y|x@ z91|^u2YSTtoTWGk=*)yU*#%*R4xTP_N;C`4^Wo4D4iq>V;bp_8lLFax2r?HLgyMn> zK*C0|NCYodh?MLU?#&JlMZqGuHXKKP54OmGDxkx}&R*UU4iSg)vY`h@;%q3w2u}wJ zk&SpT1Q@UTY0RM{uFX7ZR(4x%hozOdi|4`Bme&ji5u2Y!i0-R6E)pZ}PM5!vOjqc-yLQpu+ny6WCvNhT`zuk0;-FL|mNF zqV`9(h2lf8f&$u)1=PD2KO^JY4jyhaI`u2B?gxC|&I{R17BbnKwQ3`u4!j74!# ziDJA~aM4uB^7EcWo|5Osx{`l=0>V{R78H*vD{I)={W(__6gP?;ze!&kBPzm4z%qw< zAsG_vUjEbXR#+eRF{kXfO*g2ua2ff^TK&Jr^nc%Cf_9MlAqTnDS|EASU%F&MoJO*N z-H{L+AIMp<{aZ$NqOHDD6SpBo-TDi>!}Cj34%yIWI{xIA%5M7Odl#e|tuFTNNycqe z!#KZBsLBj9e{bt~d%McofgJ3CnlPNFmJZNAOxO)fA(INTkv~oZ%3rX7 z53d*iS0n%d;SS|9B2bUHC<~z;u22?2-I%T{gu3NdSqOE1q_Pm|wmoGb)ICwkLa5tO zl!Z`-`^rM7GiPNX)PbI|5b7*NSqQZ)tt^Dv3sn|EZ6YZPp}w9g3!%PNC<~z!Rapo% zag~Ko?FCd$*F*pi4y`MI7A615wr2ozn1h483jomJaOWr*mCNJGAMWCSu|p4d(OA*K F{{d}DTw4GD diff --git a/packages/varsig/src/__tests__/canonicalization.test.ts b/packages/varsig/src/__tests__/canonicalization.test.ts index b5f861a2..0b569074 100644 --- a/packages/varsig/src/__tests__/canonicalization.test.ts +++ b/packages/varsig/src/__tests__/canonicalization.test.ts @@ -96,7 +96,7 @@ test('EIP712', () => { const canonicalization = CanonicalizationDecoder.read( tape, hashingAlgoByKind(HashingKind.KECCAK256), - SigningKind.SECP256K1 + SigningKind.SECP256K1, ) expect(canonicalization.kind).toEqual(CanonicalizationKind.EIP712) if (canonicalization.kind !== CanonicalizationKind.EIP712) throw new Error() @@ -107,7 +107,11 @@ test('EIP712', () => { test('EIP191', () => { const bytes = concat([encode(0xe191)[0]]) const tape = new BytesTape(bytes) - const result = CanonicalizationDecoder.read(tape, hashingAlgoByKind(HashingKind.KECCAK256), SigningKind.SECP256K1) + const result = CanonicalizationDecoder.read( + tape, + hashingAlgoByKind(HashingKind.KECCAK256), + SigningKind.SECP256K1, + ) expect(result.kind).toEqual(CanonicalizationKind.EIP191) const canonicalized = toString(result('Hello'), 'hex') expect(canonicalized).toEqual('aa744ba2ca576ec62ca0045eca00ad3917fdf7ffa34fbbae50828a5a69c1580e') diff --git a/packages/varsig/src/__tests__/canons/eip712.test.ts b/packages/varsig/src/__tests__/canons/eip712.test.ts index 3007856a..705d4f53 100644 --- a/packages/varsig/src/__tests__/canons/eip712.test.ts +++ b/packages/varsig/src/__tests__/canons/eip712.test.ts @@ -48,7 +48,7 @@ const testData = { const expectedHash = uint8arrays.fromString( '703012a88c79c0ae106c7e0bd144d39d63304df1815e6d11b19189aff3dce0c4', - 'base16' + 'base16', ) test('Encode eip712 message', () => { diff --git a/packages/varsig/src/__tests__/canons/jws.test.ts b/packages/varsig/src/__tests__/canons/jws.test.ts index 2737876c..4bcb2632 100644 --- a/packages/varsig/src/__tests__/canons/jws.test.ts +++ b/packages/varsig/src/__tests__/canons/jws.test.ts @@ -6,29 +6,31 @@ import { HashingDecoder } from '../../hashing.js' import { MAGIC } from '../../magic.js' //const testJws = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' -const testJws = 'eyJhbGciOiAiRVMyNTYifQ.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const testJws = + 'eyJhbGciOiAiRVMyNTYifQ.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' - -const expectedHashInput = uint8arrays.fromString( - testJws.slice(0, testJws.lastIndexOf('.')) -) +const expectedHashInput = uint8arrays.fromString(testJws.slice(0, testJws.lastIndexOf('.'))) test('Encode jws message', () => { // @ts-ignore const node = fromOriginal(testJws) expect(node._sig.length).toEqual(56) + // @ts-ignore delete node._sig expect(node).toEqual({ sub: '1234567890', name: 'John Doe', - iat: 1516239022 + iat: 1516239022, }) }) test('Encode jws with unordered fields fails', () => { - const unorderedJws = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' - expect(() => fromOriginal(unorderedJws)).toThrowError(/Invalid JOSE payload: Varsig only supports JSON with ordered keys, got/) + const unorderedJws = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + expect(() => fromOriginal(unorderedJws)).toThrow( + /Invalid JOSE payload: Varsig only supports JSON with ordered keys, got/, + ) }) test('Canonicalize ipld jws object', () => { @@ -47,6 +49,7 @@ test('Canonicalize ipld jws object', () => { expect(tape.remainder.length).toEqual(32) // @ts-ignore delete node._sig + // @ts-ignore const sigInput = can(node) expect(sigInput).toEqual(hashing.digest(expectedHashInput)) }) diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts index 109fe6a0..e0bbef13 100644 --- a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts +++ b/packages/varsig/src/__tests__/eip712-secp256k1.test.ts @@ -87,7 +87,7 @@ describe('eip712-secp256k1.car', () => { originalKlone.signature = '0x' + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - uint8arrays.toString(uint8arrays.concat([r, s, [originalKlone.signature.v]]), 'hex') + uint8arrays.toString(uint8arrays.concat([r, s, new Uint8Array([originalKlone.signature.v])]), 'hex') } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument diff --git a/packages/varsig/src/__tests__/gen-vectors.ts b/packages/varsig/src/__tests__/gen-vectors.ts index a2b05be9..5d747a20 100644 --- a/packages/varsig/src/__tests__/gen-vectors.ts +++ b/packages/varsig/src/__tests__/gen-vectors.ts @@ -91,7 +91,7 @@ const EAS_DATA = { } const ACCOUNT = privateKeyToAccount( - '0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504' + '0x9727992a9c7d4e4b7c3b2d8d3c4b5b2e9d6e9c0a3a0e0d0c0b0a090807060504', ) function putEntry(car: CAR, eip712: any, node: any, signer: Signer, error?: string): CID { @@ -115,7 +115,7 @@ async function main() { // eslint-disable-next-line @typescript-eslint/no-unsafe-call putEntry(car, EAS_DATA, fromOriginal(EAS_DATA), { address: '0x3e95B8E249c4536FE1db2E4ce5476010767C0A05', - }) + }), ) // invalid stuff const invalidData1 = { @@ -133,8 +133,8 @@ async function main() { { address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', }, - 'Invalid signature' - ) + 'Invalid signature', + ), ) // @ts-expect-error @@ -150,8 +150,8 @@ async function main() { { address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', }, - 'Unsupported key type' - ) + 'Unsupported key type', + ), ) // @ts-expect-error // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call @@ -166,8 +166,8 @@ async function main() { { address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', }, - 'Missing recovery bit' - ) + 'Missing recovery bit', + ), ) // @ts-expect-error // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call @@ -182,8 +182,8 @@ async function main() { { address: '0x7821B4697401EdC27aB2719FF4d7a6A7737D28C3', }, - 'Unsupported hash type' - ) + 'Unsupported hash type', + ), ) car.put( @@ -193,7 +193,7 @@ async function main() { hash: 'keccak256', entries, }, - { isRoot: true } + { isRoot: true }, ) const carFilepath = new URL('./__vectors__/eip712-secp256k1.car', import.meta.url) diff --git a/packages/varsig/src/__tests__/jws-es256.test.ts b/packages/varsig/src/__tests__/jws-es256.test.ts index 1a63e575..e7bce6be 100644 --- a/packages/varsig/src/__tests__/jws-es256.test.ts +++ b/packages/varsig/src/__tests__/jws-es256.test.ts @@ -2,14 +2,9 @@ import { readFile } from 'node:fs/promises' import { expect, test } from '@jest/globals' import { CARFactory, type CAR } from 'cartonne' import { CID } from 'multiformats/cid' -import * as uint8arrays from 'uint8arrays' import { JWS } from '../canons/jws.js' import { verify, toOriginal } from '../varsig.js' -import { MAGIC } from '../magic.js' -import { BytesTape } from '../bytes-tape.js' import { klona } from 'klona' -import * as varintes from 'varintes' - const factory = new CARFactory() @@ -27,12 +22,11 @@ describe('jws.car', () => { entries = root.entries as Array }) - test('GenerateVectors', async () => { - await createVectors() - }) - + // test('GenerateVectors', async () => { + // await createVectors() + // }) - test.skip('Verify signatures', async () => { + test('Verify signatures', async () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) @@ -55,19 +49,32 @@ describe('jws.car', () => { } }) - test.skip('Create varsig ipld node', () => { -// for (const entryCID of entries) { + test('Create varsig ipld node', () => { + for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -// const entry = car.get(entryCID) + const entry = car.get(entryCID) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access -// if (!entry.original) continue + if (!entry.original) continue // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument -// const original = car.get(entry.original) - // const varsigNode = Eip712.fromOriginal(original as Eip712) + const original = car.get(entry.original) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument -// const node = car.get(entry.node) -// expect(varsigNode).toEqual(node) -// } + const node = car.get(entry.node) + if (entry.valid) { + const varsigNode = JWS.fromOriginal(original) + expect(varsigNode).toEqual(node) + } else { + if (entry.error === 'Missing alg in protected header') { + expect(() => JWS.fromOriginal(original)).toThrow('Missing alg in protected header') + } else { + const varsigNode = JWS.fromOriginal(original) + if (entry.error === 'Invalid signature') { + expect(varsigNode).toEqual(node) + } else { + expect(varsigNode).not.toEqual(node) + } + } + } + } }) test.skip('Recover original from ipld node', async () => { @@ -82,131 +89,129 @@ describe('jws.car', () => { const node = car.get(entry.node) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const originalKlone = klona(originalExpected) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access - if (Object.keys(originalKlone.signature).includes('r')) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const r = uint8arrays.fromString(originalKlone.signature.r.replace(/^0x/, ''), 'hex') - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const s = uint8arrays.fromString(originalKlone.signature.s.replace(/^0x/, ''), 'hex') - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - originalKlone.signature = - '0x' + - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - uint8arrays.toString(uint8arrays.concat([r, s, [originalKlone.signature.v]]), 'hex') - } + console.log('ori', originalKlone) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument - const originalRecovered = await toOriginal(node) - expect(originalRecovered).toEqual(originalKlone) + if (entry.valid) { + const originalRecovered = await toOriginal(node) + console.log('rec', originalRecovered) + expect(originalRecovered).toEqual(originalKlone) + } else { + console.log(entry) + expect(toOriginal(node)).rejects.toThrow() + } } }) }) - -import * as jose from 'jose' -import { generateKeyPairSync } from 'node:crypto' -import { encode, decode } from '@ipld/dag-json' -import { pipeline } from "node:stream/promises"; -import * as fs from "node:fs"; - -async function createVectors() { - const car = factory.build() - const entries = [] - await gen('ec', { namedCurve: 'P-256' }, 'ES256') - await gen('ec', { namedCurve: 'secp256k1' }, 'ES256K') - await gen('ed25519', {}, 'EdDSA') - // await gen('ed25519', {}, 'EdDSA', 'ed25519') - // await gen('ed448', {}, 'EdDSA', 'ed448') - - console.log(entries) - - car.put({ - entries, - canonicalization: 'jws', - hash: ['sha2-256', 'shake-256'], - signature: ['es256', 'secp256k1', 'ed25519', 'ed448'] - }, { isRoot: true }) - - await pipeline(car, fs.createWriteStream("./jws.car")); - - async function gen(name, opt, alg, crv) { - const kp = generateKeyPairSync(name, opt) - const {x, y } = kp.publicKey.export({ format: 'jwk' }) - const verificationKey = y ? - uint8arrays.concat([ - [0x04], - uint8arrays.fromString(x, 'base64url'), - uint8arrays.fromString(y, 'base64url') - ]) : - uint8arrays.fromString(x, 'base64url') - - const payload = JSON.parse(uint8arrays.toString(encode({ - testLink: CID.parse('bafyqacnbmrqxgzdgdeaui'), - iat:1707403055, - aud:"urn:example:audience", - // exp:1707410255 - }))) - const jwt = await new jose.SignJWT(payload) - .setProtectedHeader({ alg }) - .sign(kp.privateKey) - - - - const node = JWS.fromOriginal(jwt) - - await expect(verify(node, verificationKey)).resolves.toEqual(true) - console.log('passed one') - - const entry1 = car.put({ - valid: true, - signer: { verificationKey }, - node: car.put(node), - original: car.put(jwt) - }) - const nodeKeccak = klona(node) - let tape = new BytesTape(nodeKeccak._sig) - tape.read(1); tape.readVarint(); - const hashPosition = tape.position - nodeKeccak._sig.set([MAGIC.KECCAK_256], hashPosition) // TODO - fix - const entry2 = car.put({ - valid: false, - error: 'Invalid hash code', - signer: { verificationKey }, - node: car.put(nodeKeccak), - original: car.put(jwt) - }) - const jwtInvalid = jwt.substring(0, jwt.length - 10) + 'abc' + jwt.substring(jwt.length - 7) - const entry3 = car.put({ - valid: false, - error: 'Invalid signature', - signer: { verificationKey }, - node: car.put(JWS.fromOriginal(jwtInvalid)), - original: car.put(jwtInvalid) - }) - const invalidProtectedBytes = uint8arrays.fromString(JSON.stringify({})) - const invalidProtected = uint8arrays.toString(invalidProtectedBytes, 'base64url') - const jwtMissingAlg = invalidProtected + jwt.substring(jwt.indexOf('.')) - const nodeMissingAlg = klona(node) - const protectedLength = varintes.encode(invalidProtectedBytes.length)[0] - tape = new BytesTape(nodeKeccak._sig) - tape.read(1); tape.readVarint(); tape.readVarint(); tape.readVarint(); - const protLenPos = tape.position - const klonLength = tape.readVarint() - const signature = nodeMissingAlg._sig.slice(tape.position + klonLength) - const newVarsig = uint8arrays.concat([ - nodeMissingAlg._sig.slice(0, protLenPos), - varintes.encode(invalidProtectedBytes.length)[0], - invalidProtectedBytes, - signature - ]) - nodeMissingAlg._sig = newVarsig - const entry4 = car.put({ - valid: false, - error: 'Missing alg in protected header', - signer: { verificationKey }, - node: car.put(nodeMissingAlg), - original: car.put(jwtMissingAlg) - }) - entries.push(entry1, entry2, entry3, entry4) - } -} +// import * as jose from 'jose' +// import { generateKeyPairSync } from 'node:crypto' +// import { encode, decode } from '@ipld/dag-json' +// import { pipeline } from "node:stream/promises"; +// import * as fs from "node:fs"; +// import * as varintes from 'varintes' +// import * as uint8arrays from 'uint8arrays' +// import { MAGIC } from '../magic.js' +// import { BytesTape } from '../bytes-tape.js' +// +// async function createVectors() { +// const car = factory.build() +// const entries = [] +// await gen('ec', { namedCurve: 'P-256' }, 'ES256') +// await gen('ec', { namedCurve: 'secp256k1' }, 'ES256K') +// await gen('ed25519', {}, 'EdDSA') +// await gen('ed25519', {}, 'EdDSA', 'ed25519') +// // await gen('ed448', {}, 'EdDSA', 'ed448') +// +// console.log(entries) +// +// car.put({ +// entries, +// canonicalization: 'jws', +// hash: ['sha2-256', 'shake-256'], +// signature: ['es256', 'secp256k1', 'ed25519', 'ed448'] +// }, { isRoot: true }) +// +// await pipeline(car, fs.createWriteStream("./jws.car")); +// +// async function gen(name, opt, alg, crv) { +// const kp = generateKeyPairSync(name, opt) +// const {x, y } = kp.publicKey.export({ format: 'jwk' }) +// const verificationKey = y ? +// uint8arrays.concat([ +// [0x04], +// uint8arrays.fromString(x, 'base64url'), +// uint8arrays.fromString(y, 'base64url') +// ]) : +// uint8arrays.fromString(x, 'base64url') +// +// const payload = JSON.parse(uint8arrays.toString(encode({ +// testLink: CID.parse('bafyqacnbmrqxgzdgdeaui'), +// iat:1707403055, +// aud:"urn:example:audience", +// // exp:1707410255 +// }))) +// const jwt = await new jose.SignJWT(payload) +// .setProtectedHeader({ alg }) +// .sign(kp.privateKey) +// +// +// +// const node = JWS.fromOriginal(jwt) +// +// // await expect(verify(node, verificationKey)).resolves.toEqual(true) +// // console.log('passed one') +// +// const entry1 = car.put({ +// valid: true, +// signer: { verificationKey }, +// node: car.put(node), +// original: car.put(jwt) +// }) +// const nodeKeccak = klona(node) +// let tape = new BytesTape(nodeKeccak._sig) +// tape.read(1); tape.readVarint(); +// const hashPosition = tape.position +// nodeKeccak._sig.set([MAGIC.KECCAK_256], hashPosition) // TODO - fix +// const entry2 = car.put({ +// valid: false, +// error: 'Invalid hash code', +// signer: { verificationKey }, +// node: car.put(nodeKeccak), +// original: car.put(jwt) +// }) +// const jwtInvalid = jwt.substring(0, jwt.length - 10) + 'abc' + jwt.substring(jwt.length - 7) +// const entry3 = car.put({ +// valid: false, +// error: 'Invalid signature', +// signer: { verificationKey }, +// node: car.put(JWS.fromOriginal(jwtInvalid)), +// original: car.put(jwtInvalid) +// }) +// const invalidProtectedBytes = uint8arrays.fromString(JSON.stringify({})) +// const invalidProtected = uint8arrays.toString(invalidProtectedBytes, 'base64url') +// const jwtMissingAlg = invalidProtected + jwt.substring(jwt.indexOf('.')) +// const nodeMissingAlg = klona(node) +// const protectedLength = varintes.encode(invalidProtectedBytes.length)[0] +// tape = new BytesTape(nodeKeccak._sig) +// tape.read(1); tape.readVarint(); tape.readVarint(); tape.readVarint(); +// const protLenPos = tape.position +// const klonLength = tape.readVarint() +// const signature = nodeMissingAlg._sig.slice(tape.position + klonLength) +// const newVarsig = uint8arrays.concat([ +// nodeMissingAlg._sig.slice(0, protLenPos), +// varintes.encode(invalidProtectedBytes.length)[0], +// invalidProtectedBytes, +// signature +// ]) +// nodeMissingAlg._sig = newVarsig +// const entry4 = car.put({ +// valid: false, +// error: 'Missing alg in protected header', +// signer: { verificationKey }, +// node: car.put(nodeMissingAlg), +// original: car.put(jwtMissingAlg) +// }) +// entries.push(entry1, entry2, entry3, entry4) +// } +// } diff --git a/packages/varsig/src/__tests__/parse-eip191.test.ts b/packages/varsig/src/__tests__/parse-eip191.test.ts index df48d659..83586175 100644 --- a/packages/varsig/src/__tests__/parse-eip191.test.ts +++ b/packages/varsig/src/__tests__/parse-eip191.test.ts @@ -9,13 +9,13 @@ import { hex } from './hex.util.js' test('validate eip191', async () => { const account = privateKeyToAccount( - '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', ) const verificationKey = uint8arrays.fromString(account.publicKey.slice(2), 'base16') const stringSignature = await account.signMessage({ message: 'Hello World' }) const signatureBytes = uint8arrays.fromString( stringSignature.toLowerCase().replace(/^0x/, ''), - 'hex' + 'hex', ) const varsig = uint8arrays.concat([ hex(0x34), @@ -35,18 +35,18 @@ test('validate eip191', async () => { test('validate eip191, no recovery bit', async () => { const account = privateKeyToAccount( - '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', ) const verificationKey = uint8arrays.fromString(account.publicKey.slice(2), 'base16') const stringSignature = await account.signMessage({ message: 'Hello World' }) const signatureBytes = uint8arrays.fromString( stringSignature.toLowerCase().replace(/^0x/, ''), - 'hex' + 'hex', ) const varsig = uint8arrays.concat([ hex(0x34), varintes.encode(0xe7)[0], - [0x00], + new Uint8Array([0x00]), varintes.encode(0x1b)[0], varintes.encode(CanonicalizationKind.EIP191)[0], signatureBytes.subarray(0, 64), diff --git a/packages/varsig/src/__tests__/parse-eip712.test.ts b/packages/varsig/src/__tests__/parse-eip712.test.ts index e451bc6a..1e1b164a 100644 --- a/packages/varsig/src/__tests__/parse-eip712.test.ts +++ b/packages/varsig/src/__tests__/parse-eip712.test.ts @@ -77,7 +77,7 @@ const testData = { test('712 flow', async () => { const account = privateKeyToAccount( - '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', ) const stringSignature = await account.signTypedData({ domain: { ...testData.domain, chainId: 1n }, diff --git a/packages/varsig/src/bytes.ts b/packages/varsig/src/bytes.ts index f15eba2c..4afac9fa 100644 --- a/packages/varsig/src/bytes.ts +++ b/packages/varsig/src/bytes.ts @@ -34,7 +34,7 @@ export function toBytes(varsig: VarsigBytes): Uint8Array { const hashingLen = varintes.encodingLength(varsig.hashing) const signingLen = varintes.encodingLength(varsig.signing) const result = new Uint8Array( - 1 + encodingLen + hashingLen + signingLen + varsig.signature.byteLength + 1 + encodingLen + hashingLen + signingLen + varsig.signature.byteLength, ) result.set(VARSIG_SIGIL_BYTES, 0) varintes.encode(varsig.encoding, result, 1) diff --git a/packages/varsig/src/canonicalization.ts b/packages/varsig/src/canonicalization.ts index 686d87df..c62dd5eb 100644 --- a/packages/varsig/src/canonicalization.ts +++ b/packages/varsig/src/canonicalization.ts @@ -19,6 +19,7 @@ type IpldNode = Record type CanonicalizationEIP191 = { kind: CanonicalizationKind.EIP191 (message: string): Uint8Array + original(): any } type CanonicalizationEIP712 = { @@ -27,7 +28,16 @@ type CanonicalizationEIP712 = { original(node: IpldNode, signature: Uint8Array, recoveryBit: number | undefined): any } -export type CanonicalizationAlgo = CanonicalizationEIP191 | CanonicalizationEIP712 +type CanonicalizationJWS = { + kind: CanonicalizationKind.JWS + (message: any): Uint8Array + original(node: IpldNode, signature: Uint8Array): any +} + +export type CanonicalizationAlgo = + | CanonicalizationEIP191 + | CanonicalizationEIP712 + | CanonicalizationJWS export class CanonicalizationDecoder { constructor(private readonly tape: BytesTape) {} @@ -44,15 +54,17 @@ export class CanonicalizationDecoder { case CanonicalizationKind.EIP712: return Eip712.prepareCanonicalization(this.tape, hashing, sigKind) case CanonicalizationKind.EIP191: { - if (hashing.kind !== HashingKind.KECCAK256) throw new Error(`EIP191 mandates use of KECCAK 256`) + if (hashing.kind !== HashingKind.KECCAK256) + throw new Error(`EIP191 mandates use of KECCAK 256`) const fn: CanonicalizationEIP191 = (message: string) => { return keccak_256( uint8arrays.fromString( - `\x19Ethereum Signed Message:\n` + String(message.length) + message - ) + `\x19Ethereum Signed Message:\n` + String(message.length) + message, + ), ) } fn.kind = CanonicalizationKind.EIP191 + fn.original = () => {} return fn } default: diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index e08f258e..6b1d86f9 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -150,7 +150,7 @@ const COMPRESSED_TO_SOLIDITY: Record = { const SOLIDITY_TO_COMPRESSED = Object.fromEntries( Object.entries(COMPRESSED_TO_SOLIDITY).map(([k, v]) => { return [v, k] - }) + }), ) const SUPPORTED_KEY_TYPES = [ @@ -164,7 +164,7 @@ const SIGIL = MAGIC.EIP712 export function prepareCanonicalization( tape: BytesTape, hashing: HashingAlgo, - keyType: SigningKind + keyType: SigningKind, ): CanonicalizationAlgo { if (hashing.kind !== SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashing}`) if (!SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) @@ -189,7 +189,7 @@ export function prepareCanonicalization( const original = (node: IpldNode, signature: Uint8Array, recoveryBit: number | undefined) => { const message = ipldNodeToMessage(node) - const sigBytes = recoveryBit ? uint8arrays.concat([signature, [recoveryBit]]) : signature + const sigBytes = recoveryBit ? uint8arrays.concat([signature, new Uint8Array([recoveryBit])]) : signature const sigHex = `0x${uint8arrays.toString(sigBytes, 'base16')}` return { ...metadata, message, signature: sigHex } } @@ -228,7 +228,7 @@ function extractSignature(signature: string | SignatureComponents) { const recoveryBit = new Uint8Array([signature.v]) const signatureBytes = uint8arrays.fromString( signature.r.slice(2) + signature.s.slice(2), - 'base16' + 'base16', ) return { recoveryBit: recoveryBit, bytes: signatureBytes } } @@ -267,7 +267,7 @@ export function fromOriginal({ function messageToIpld( message: Record, types: Eip712Types, - primaryType: string + primaryType: string, ): IpldNode { const node = {} for (const [key, value] of Object.entries(message)) { diff --git a/packages/varsig/src/canons/jws.ts b/packages/varsig/src/canons/jws.ts index 43242728..fb3472a0 100644 --- a/packages/varsig/src/canons/jws.ts +++ b/packages/varsig/src/canons/jws.ts @@ -32,7 +32,7 @@ export const JWS = { SIGIL, prepareCanonicalization, fromOriginal } export function prepareCanonicalization( tape: BytesTape, hashing: HashingAlgo, - keyType: SigningKind + keyType: SigningKind, ): CanonicalizationAlgo { const protectedLength = tape.readVarint() const protectedBytes = tape.read(protectedLength) @@ -51,6 +51,10 @@ export function prepareCanonicalization( const payloadB64u = toB64u(encode(node)) const protectedB64u = toB64u(protectedBytes) const input = uint8arrays.fromString(`${protectedB64u}.${payloadB64u}`) + if (keyType === MAGIC.ED25519) { + // ed25519 includes hashing as part of the signature validation, so we need to skip it here + return input + } return hashing.digest(input) } can.kind = SIGIL @@ -67,7 +71,11 @@ export function fromOriginal(jws: string): IpldNodeSigned { const [protectedB64u, payloadB64u, signatureB64u] = jws.split('.') const node = decode>(fromB64u(payloadB64u)) if (toB64u(encode(node)) !== payloadB64u) { - throw new Error(`Invalid JOSE payload: Varsig only supports JSON with ordered keys, got "${JSON.stringify(node)}"`) + throw new Error( + `Invalid JOSE payload: Varsig only supports JSON with ordered keys, got "${JSON.stringify( + node, + )}"`, + ) } const protectedBytes = fromB64u(protectedB64u) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment diff --git a/packages/varsig/src/hashing.ts b/packages/varsig/src/hashing.ts index 3fa031b4..4513a8fc 100644 --- a/packages/varsig/src/hashing.ts +++ b/packages/varsig/src/hashing.ts @@ -11,9 +11,7 @@ export enum HashingKind { KECCAK256 = MAGIC.KECCAK_256, } -export type HashFn = ( - input: Uint8Array -) => Uint8Array +export type HashFn = (input: Uint8Array) => Uint8Array export type HashingAlgo = { kind: HashingKind @@ -25,17 +23,17 @@ export function hashingAlgoByKind(kind: HashingKind): HashingAlgo { case HashingKind.SHA2_512: return { kind: HashingKind.SHA2_512, - digest: sha512 + digest: sha512, } case HashingKind.SHA2_256: return { kind: HashingKind.SHA2_256, - digest: sha256 + digest: sha256, } case HashingKind.KECCAK256: return { kind: HashingKind.KECCAK256, - digest: keccak_256 + digest: keccak_256, } default: throw new UnreacheableCaseError(kind, 'hashing algo') diff --git a/packages/varsig/src/signing.ts b/packages/varsig/src/signing.ts index 6f5c5bdb..c51f26d7 100644 --- a/packages/varsig/src/signing.ts +++ b/packages/varsig/src/signing.ts @@ -3,6 +3,7 @@ import type { BytesTape } from './bytes-tape.js' import { UnreacheableCaseError } from './unreachable-case-error.js' import { Secp256k1 } from './signing/secp256k1.js' import { P256 } from './signing/p256.js' +import { ED25519 } from './signing/ed25519.js' type EthAddress = `0x${string}` type PublicKey = Uint8Array @@ -12,6 +13,7 @@ export enum SigningKind { RSA = MAGIC.RSA, SECP256K1 = MAGIC.SECP256K1, P256 = MAGIC.ES256, + ED25519 = MAGIC.ED25519, } export type SigningAlgo = { @@ -23,7 +25,7 @@ export type SigningAlgo = { export type VerifySignatureFn = ( input: Uint8Array, signature: Uint8Array, - verificationKey: VerificationKey + verificationKey: VerificationKey, ) => Promise export class SigningDecoder { @@ -40,6 +42,8 @@ export class SigningDecoder { return Secp256k1.prepareVerifier(this.tape) case SigningKind.P256: return P256.prepareVerifier(this.tape) + case SigningKind.ED25519: + return ED25519.prepareVerifier(this.tape) case SigningKind.RSA: throw new Error(`Not implemented: signingSigil: RSA`) default: @@ -53,6 +57,8 @@ export class SigningDecoder { return Secp256k1.readSignature(this.tape) case SigningKind.P256: return P256.readSignature(this.tape) + case SigningKind.ED25519: + return ED25519.readSignature(this.tape) case SigningKind.RSA: { throw new Error(`Not supported: RSA`) } diff --git a/packages/varsig/src/signing/ed25519.ts b/packages/varsig/src/signing/ed25519.ts new file mode 100644 index 00000000..e8a71ff0 --- /dev/null +++ b/packages/varsig/src/signing/ed25519.ts @@ -0,0 +1,23 @@ +import { MAGIC } from '../magic.js' +import { SigningAlgo } from '../signing.js' +import { ed25519 } from '@noble/curves/ed25519' +import type { BytesTape } from '../bytes-tape.js' + +const SIGIL = MAGIC.ED25519 + +// @ts-ignore +function prepareVerifier(tape: BytesTape): SigningAlgo { + return { + kind: SIGIL, + // eslint-disable-next-line @typescript-eslint/require-await + verify: async (input, signature, verificationKey): Promise => { + return ed25519.verify(signature, input, verificationKey) + }, + } +} + +function readSignature(tape: BytesTape): Uint8Array { + return tape.read(64) +} + +export const ED25519 = { SIGIL, prepareVerifier, readSignature } diff --git a/packages/varsig/src/varsig.ts b/packages/varsig/src/varsig.ts index 14bb4865..5290c2e9 100644 --- a/packages/varsig/src/varsig.ts +++ b/packages/varsig/src/varsig.ts @@ -1,6 +1,5 @@ import { Decoder } from './decoder.js' import { BytesTape } from './bytes-tape.js' -import { CanonicalizationKind } from './canonicalization.js' import { klona } from 'klona' export { Eip712 } from './canons/eip712' @@ -17,7 +16,7 @@ type Decoded = any export async function verify( node: VarsigNode, - verificationKey: PublicKey | EthAddress + verificationKey: PublicKey | EthAddress, ): Promise { const tape = new BytesTape(node._sig) // @ts-ignore @@ -41,8 +40,6 @@ export async function toOriginal(node: VarsigNode): Promise { const clone = klona(node) // @ts-ignore delete clone._sig - if (canonicalization.kind !== CanonicalizationKind.EIP712) - throw new Error(`Supported just for EIP712`) // eslint-disable-next-line @typescript-eslint/no-unsafe-return return canonicalization.original(clone, signature, signing.recoveryBit) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed63d9a2..5bbfed2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -581,7 +581,7 @@ importers: dependencies: '@ipld/dag-json': specifier: ^10.1.5 - version: 10.1.5 + version: 10.1.7 '@noble/curves': specifier: ^1.2.0 version: 1.2.0 @@ -607,11 +607,11 @@ importers: specifier: ^2.0.6 version: 2.0.6 multiformats: - specifier: ^11.0.2 - version: 11.0.2 + specifier: ^13.0.1 + version: 13.0.1 uint8arrays: - specifier: ^4.0.3 - version: 4.0.3 + specifier: ^5.0.2 + version: 5.0.2 varintes: specifier: ^2.0.5 version: 2.0.5 @@ -623,8 +623,8 @@ importers: specifier: ^1.0.2 version: 1.0.2 cartonne: - specifier: ^2.2.0 - version: 2.2.0 + specifier: ^3.0.1 + version: 3.0.1 tsm: specifier: ^2.3.0 version: 2.3.0 @@ -2279,7 +2279,7 @@ packages: '@ceramicnetwork/common': 3.2.0 '@ethersproject/abi': 5.7.0 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding dev: false @@ -2293,7 +2293,7 @@ packages: '@stablelib/sha256': 1.0.1 caip: 1.1.0 near-api-js: 0.44.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding dev: false @@ -2314,7 +2314,7 @@ packages: '@zondax/filecoin-signing-tools': 0.18.6 caip: 1.1.0 tweetnacl: 1.0.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - '@polkadot/util' - bufferutil @@ -2331,7 +2331,7 @@ packages: codeco: 1.2.0 dag-jose: 4.0.0 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /@ceramicnetwork/common@3.2.0: resolution: {integrity: sha512-3ql9LN/AXO2VxosnNzTeaMo+JRQpCclwFhyfWrpH0okLdiB1CeDDOkiKNOvz+6ZtYql1NpIby1/Ag5ovtxgQjg==} @@ -2352,7 +2352,7 @@ packages: logfmt: 1.3.2 multiformats: 11.0.2 rxjs: 7.8.1 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding @@ -2407,7 +2407,7 @@ packages: pg: 8.11.0 rxjs: 7.8.1 sqlite3: 5.1.6 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - '@polkadot/util' - better-sqlite3 @@ -2491,7 +2491,7 @@ packages: resolution: {integrity: sha512-sykfBW9oTkRdTMoy6jSf1QxH9et7cvgHBjvOVs5kfR/eh4zB1djJ9VaXC5HZ4wbzrTYjpFw9eNiGiRoJwhkcSQ==} dependencies: '@stablelib/sha256': 1.0.1 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /@ceramicnetwork/pinning-ipfs-backend@3.2.0: @@ -2499,7 +2499,7 @@ packages: dependencies: '@stablelib/sha256': 1.0.1 ipfs-http-client: 60.0.1 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding - supports-color @@ -2555,7 +2555,7 @@ packages: json-ptr: 3.1.1 lodash.clonedeep: 4.5.0 lodash.ismatch: 4.4.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding dev: false @@ -2572,7 +2572,7 @@ packages: fast-json-patch: 3.1.1 least-recent: 1.0.3 lodash.clonedeep: 4.5.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding dev: false @@ -2586,7 +2586,7 @@ packages: '@stablelib/random': 1.0.2 fast-json-patch: 3.1.1 object-sizeof: 2.6.1 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding @@ -2604,7 +2604,7 @@ packages: fast-json-patch: 3.1.1 json-schema-typed: 8.0.1 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding @@ -2620,7 +2620,7 @@ packages: fast-json-patch: 3.1.1 least-recent: 1.0.3 lodash.clonedeep: 4.5.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding dev: false @@ -2635,7 +2635,7 @@ packages: dids: 4.0.1 fast-json-patch: 3.1.1 lodash.clonedeep: 4.5.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding @@ -2685,7 +2685,7 @@ packages: multiformats: 11.0.2 protobufjs: 6.11.3 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -2712,7 +2712,7 @@ packages: it-stream-types: 1.0.5 protons-runtime: 5.0.0(uint8arraylist@2.4.3) uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -2931,7 +2931,7 @@ packages: apg-js: 4.1.3 caip: 1.1.0 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /@didtools/cacao@2.1.0: resolution: {integrity: sha512-35gopj+mOmAlA3nHoHiYMvNMXJtbJDJnVpIlCf/Wf/+/x+uG9aIQefXfF35D6JuaTCZ0apabjpT2umL5h3EXcw==} @@ -2942,7 +2942,7 @@ packages: '@ipld/dag-cbor': 9.0.7 caip: 1.1.0 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /@didtools/codecs@1.0.1: resolution: {integrity: sha512-6PYXOCX7mwVWUcudKQ3eW5LtI8v5esozazbf2q2F01PE+LoeEvTytvgU9FEspj4pATpq3hPx1eenX2uLirDJ8w==} @@ -2950,7 +2950,7 @@ packages: dependencies: codeco: 1.2.0 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /@didtools/pkh-ethereum@0.1.0: resolution: {integrity: sha512-Abmc6uvWU8zkOrQbPUAsRtTW293vhx+rzd+/bbduTLrRGEqZ3niakQkxMqvQKZ6/9w+n0IjQVXSHE5vzc5cAeg==} @@ -2969,7 +2969,7 @@ packages: '@ethersproject/wallet': 5.7.0 '@stablelib/random': 1.0.2 caip: 1.1.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /@didtools/pkh-solana@0.1.1: resolution: {integrity: sha512-2Sn4xSg8otqAeXA0tDYUM+3KQtzOr2gBcu0wbJyOn/30Ocj3jxHFQg7NfumEsiQtQ0HtnmsGZUrnCgoxHqLwWg==} @@ -2979,7 +2979,7 @@ packages: '@noble/curves': 1.3.0 '@stablelib/random': 1.0.2 caip: 1.1.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /@didtools/pkh-stacks@0.1.0: resolution: {integrity: sha512-dEgyHleiIa2afibchNqs07tSqddFS6pX9D5BNxbWH0NAr+FisVCA4nUXajcbd9TUbSuplClfQ4EXjjJAGqlgeg==} @@ -3001,10 +3001,10 @@ packages: dependencies: '@didtools/cacao': 2.1.0 '@noble/curves': 1.3.0 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@stablelib/random': 1.0.2 caip: 1.1.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /@didtools/siwx@1.0.0: resolution: {integrity: sha512-b7sPDTNHdySoJ+Rp2p06x3rg1iTxI4yPTTA3PrPh40xcvFJ0K/YhdIb/Rzff13t92arcJ+VYGFhqtJorauV91g==} @@ -4383,14 +4383,14 @@ packages: engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: cborg: 4.0.6 - multiformats: 13.0.0 + multiformats: 13.0.1 - /@ipld/dag-json@10.1.5: - resolution: {integrity: sha512-AIIDRGPgIqVG2K1O42dPDzNOfP0YWV/suGApzpF+YWZLwkwdGVsxjmXcJ/+rwOhRGdjpuq/xQBKPCu1Ao6rdOQ==} + /@ipld/dag-json@10.1.7: + resolution: {integrity: sha512-ipraTPMA40sZAtUYwFvjHeQjReDJXWI8V3lrOeyedKxMb9rOOCS0B7eodRoWM3RIS2qMqtnu1oZr8kP+QJEN0Q==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: cborg: 4.0.6 - multiformats: 12.1.3 + multiformats: 13.0.1 dev: false /@ipld/dag-pb@4.0.3: @@ -4734,7 +4734,7 @@ packages: node-forge: 1.3.1 protons-runtime: 5.0.0(uint8arraylist@2.4.3) uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /@libp2p/delegated-content-routing@4.0.5: @@ -4783,7 +4783,7 @@ packages: '@libp2p/pubsub': 6.0.6 protons-runtime: 5.0.0(uint8arraylist@2.4.3) uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -5115,7 +5115,7 @@ packages: protons-runtime: 5.0.0(uint8arraylist@2.4.3) timeout-abort-controller: 3.0.0 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 varint: 6.0.0 transitivePeerDependencies: - supports-color @@ -5168,7 +5168,7 @@ packages: it-stream-types: 1.0.5 rate-limiter-flexible: 2.4.1 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 varint: 6.0.0 transitivePeerDependencies: - supports-color @@ -5190,7 +5190,7 @@ packages: it-reader: 6.0.4 it-stream-types: 2.0.1 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -5214,7 +5214,7 @@ packages: multiformats: 11.0.2 protons-runtime: 5.0.0(uint8arraylist@2.4.3) uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /@libp2p/peer-id@2.0.3: @@ -5224,7 +5224,7 @@ packages: '@libp2p/interface-peer-id': 2.0.2 '@libp2p/interfaces': 3.3.2 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /@libp2p/peer-record@5.0.3: @@ -5241,7 +5241,7 @@ packages: protons-runtime: 5.0.0(uint8arraylist@2.4.3) uint8-varint: 1.0.6 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -5268,7 +5268,7 @@ packages: multiformats: 11.0.2 protons-runtime: 5.0.0(uint8arraylist@2.4.3) uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -5308,7 +5308,7 @@ packages: multiformats: 11.0.2 p-queue: 7.3.4 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -5322,7 +5322,7 @@ packages: multiformats: 11.0.2 protons-runtime: 5.0.0(uint8arraylist@2.4.3) uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -5397,7 +5397,7 @@ packages: it-stream-types: 1.0.5 p-defer: 4.0.0 p-event: 5.0.1 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -5437,7 +5437,7 @@ packages: multiformats: 11.0.2 p-defer: 4.0.0 socket.io-client: 4.6.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - bufferutil - supports-color @@ -5595,7 +5595,7 @@ packages: dns-over-http-resolver: 2.1.1 err-code: 3.0.1 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 varint: 6.0.0 transitivePeerDependencies: - supports-color @@ -5610,7 +5610,7 @@ packages: '@libp2p/interfaces': 3.3.2 dns-over-http-resolver: 2.1.1 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 varint: 6.0.0 transitivePeerDependencies: - supports-color @@ -8353,7 +8353,7 @@ packages: /bip39@3.1.0: resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 /bl@5.1.0: resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} @@ -8566,7 +8566,7 @@ packages: /bs58check@3.0.1: resolution: {integrity: sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==} dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 bs58: 5.0.0 dev: true @@ -8642,7 +8642,7 @@ packages: resolution: {integrity: sha512-rpwfAcS/CMqo0oCqDf3r9eeLgScRE3l/xHDCXhM3UyrfvIn7PrLq63uHh7yYbv8NzaZn5MVsVhIRpQ+5GZ5HyA==} engines: {node: '>=8'} dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 base-x: 4.0.0 /cacache@15.3.0: @@ -8799,6 +8799,15 @@ packages: multihashes-sync: 1.1.1 varintes: 2.0.5 + /cartonne@3.0.1: + resolution: {integrity: sha512-Y8DH//DthEUbfvOMGYj/9K3F1RcWkiVu2dB9tGkiBnMqojAXTpu+TUs9FNNx202H0TQdJgbPsQl7Q6NuJ48dCw==} + dependencies: + '@ipld/dag-cbor': 9.0.7 + multiformats: 13.0.1 + multihashes-sync: 2.0.0 + varintes: 2.0.5 + dev: true + /catering@2.1.1: resolution: {integrity: sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==} engines: {node: '>=6'} @@ -9594,7 +9603,7 @@ packages: resolution: {integrity: sha512-bmmXtVdEKp/zYH8El4GGkMREJioUztz8fzOErfy5dTbyKIVOF61C5sfsZLYCB/wiT/I9+SPNrQeo/Cx6Ik3wJQ==} dependencies: '@ipld/dag-cbor': 9.0.7 - multiformats: 13.0.0 + multiformats: 13.0.1 dev: false /dag-jose@4.0.0: @@ -9627,7 +9636,7 @@ packages: it-pipe: 2.0.5 it-pushable: 3.1.3 it-take: 2.0.1 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -9673,7 +9682,7 @@ packages: '@libp2p/logger': 2.1.1 datastore-core: 8.0.4 interface-datastore: 7.0.4 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -9978,7 +9987,7 @@ packages: did-resolver: 4.1.0 multiformats: 11.0.2 rpc-utils: 0.6.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 /diff-sequences@29.4.3: resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} @@ -11965,7 +11974,7 @@ packages: engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: sparse-array: 1.3.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /handle-thing@2.0.1: @@ -12506,7 +12515,7 @@ packages: dependencies: interface-store: 3.0.4 nanoid: 4.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /interface-datastore@8.2.3: @@ -12515,7 +12524,7 @@ packages: dependencies: interface-store: 5.1.2 nanoid: 4.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /interface-store@3.0.4: @@ -12602,7 +12611,7 @@ packages: protobufjs: 7.2.3 readable-stream: 4.4.0 timeout-abort-controller: 3.0.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 varint: 6.0.0 varint-decoder: 1.0.0 transitivePeerDependencies: @@ -12635,7 +12644,7 @@ packages: it-drain: 2.0.1 it-foreach: 1.0.1 p-queue: 7.3.4 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - bufferutil - encoding @@ -12686,7 +12695,7 @@ packages: nanoid: 4.0.2 parse-duration: 1.1.0 timeout-abort-controller: 3.0.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding - supports-color @@ -12700,7 +12709,7 @@ packages: '@chainsafe/libp2p-noise': 11.0.4 '@ipld/car': 5.1.1 '@ipld/dag-cbor': 9.0.7 - '@ipld/dag-json': 10.1.5 + '@ipld/dag-json': 10.1.7 '@ipld/dag-pb': 4.0.3 '@libp2p/bootstrap': 6.0.3 '@libp2p/crypto': 1.0.17 @@ -12768,7 +12777,7 @@ packages: pako: 2.1.0 parse-duration: 1.1.0 timeout-abort-controller: 3.0.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - bufferutil - encoding @@ -12782,7 +12791,7 @@ packages: deprecated: js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details dependencies: '@ipld/dag-cbor': 9.0.7 - '@ipld/dag-json': 10.1.5 + '@ipld/dag-json': 10.1.7 '@ipld/dag-pb': 4.0.3 '@libp2p/logger': 2.1.1 '@libp2p/peer-id': 2.0.3 @@ -12799,7 +12808,7 @@ packages: multiformats: 11.0.2 parse-duration: 1.1.0 stream-to-it: 0.2.4 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding - supports-color @@ -12820,7 +12829,7 @@ packages: it-length: 2.0.1 multiformats: 11.0.2 protobufjs: 7.2.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 varint: 6.0.0 transitivePeerDependencies: - supports-color @@ -12856,7 +12865,7 @@ packages: proper-lockfile: 4.1.2 quick-lru: 6.1.1 sort-keys: 5.0.0 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -12879,7 +12888,7 @@ packages: it-pushable: 3.1.3 multiformats: 11.0.2 p-queue: 7.3.4 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /ipfs-unixfs-importer@12.0.1: @@ -12900,7 +12909,7 @@ packages: multiformats: 11.0.2 rabin-wasm: 0.1.5 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - encoding - supports-color @@ -12955,7 +12964,7 @@ packages: protons-runtime: 4.0.2(uint8arraylist@2.4.3) timestamp-nano: 1.0.1 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -13120,7 +13129,7 @@ packages: '@multiformats/multiaddr': 11.6.1 iso-url: 1.2.1 multiformats: 11.0.2 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 transitivePeerDependencies: - supports-color dev: false @@ -13551,7 +13560,7 @@ packages: it-stream-types: 1.0.5 uint8-varint: 1.0.6 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /it-length-prefixed@9.0.1: @@ -13562,7 +13571,7 @@ packages: it-stream-types: 2.0.1 uint8-varint: 1.0.6 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /it-length@2.0.1: @@ -13707,14 +13716,14 @@ packages: it-to-buffer: 3.0.1 p-defer: 4.0.0 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /it-to-buffer@3.0.1: resolution: {integrity: sha512-TiMudfypF2yW+HdNfhDgbkNQ42yuK1MizB716kwnzIJSQa8AM15zh+VZG2L/xQWaqyWfra1dr9neWO55xsYolA==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /it-to-stream@1.0.0: @@ -13735,7 +13744,7 @@ packages: event-iterator: 2.0.0 iso-url: 1.2.1 it-stream-types: 1.0.5 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 ws: 8.13.0 transitivePeerDependencies: - bufferutil @@ -14815,7 +14824,7 @@ packages: set-delayed-interval: 1.0.0 timeout-abort-controller: 3.0.0 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 wherearewe: 2.0.1 xsalsa20: 1.2.0 transitivePeerDependencies: @@ -15415,15 +15424,25 @@ packages: /multiformats@13.0.0: resolution: {integrity: sha512-xiIB0p7EKmETm3wyKedOg/xuyQ18PoWwXCzzgpZAiDxL9ktl3XTh8AqoDT5kAqRg+DU48XAGPsUJL2Rn6Bx3Lw==} + /multiformats@13.0.1: + resolution: {integrity: sha512-bt3R5iXe2O8xpp3wkmQhC73b/lC4S2ihU8Dndwcsysqbydqb8N+bpP116qMcClZ17g58iSIwtXUTcg2zT4sniA==} + /multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} /multihashes-sync@1.1.1: resolution: {integrity: sha512-6KCdl3Em5m8q5+06npekkaDPS1RLXV/0FRo24Q04LvRpJp50wPhQ3l5NCXaQU1TBS/v6hTRMwNtVfXqCMSEO9A==} dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 multiformats: 11.0.2 + /multihashes-sync@2.0.0: + resolution: {integrity: sha512-hoBamCqXuVmeo4NAY52dbYuUIKHy3/FcqxyKZSbhqicR2SbUjgiY4FoDvE8BV40dPfAJTT6pQpqYeuKxqKwOLQ==} + dependencies: + '@noble/hashes': 1.3.3 + multiformats: 13.0.1 + dev: true + /murmurhash3js-revisited@3.0.0: resolution: {integrity: sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==} engines: {node: '>=8.0.0'} @@ -19308,14 +19327,14 @@ packages: byte-access: 1.0.1 longbits: 1.1.0 uint8arraylist: 2.4.3 - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /uint8arraylist@2.4.3: resolution: {integrity: sha512-oEVZr4/GrH87K0kjNce6z8pSCzLEPqHNLNR5sj8cJOySrTP8Vb/pMIbZKLJGhQKxm1TiZ31atNrpn820Pyqpow==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} dependencies: - uint8arrays: 4.0.3 + uint8arrays: 4.0.10 dev: false /uint8arrays@3.1.1: @@ -19328,16 +19347,16 @@ packages: dependencies: multiformats: 12.1.3 - /uint8arrays@4.0.3: - resolution: {integrity: sha512-b+aKlI2oTnxnfeSQWV1sMacqSNxqhtXySaH6bflvONGxF8V/fT3ZlYH7z2qgGfydsvpVo4JUgM/Ylyfl2YouCg==} - engines: {node: '>=16.0.0', npm: '>=7.0.0'} - dependencies: - multiformats: 11.0.2 - /uint8arrays@5.0.1: resolution: {integrity: sha512-ND5RpJAnPgHmZT7hWD/2T4BwRp04j8NLKvMKC/7bhiEwEjUMkQ4kvBKiH6hOqbljd6qJ2xS8reL3vl1e33grOQ==} dependencies: - multiformats: 13.0.0 + multiformats: 13.0.1 + + /uint8arrays@5.0.2: + resolution: {integrity: sha512-S0GaeR+orZt7LaqzTRs4ZP8QqzAauJ+0d4xvP2lJTA99jIkKsE2FgDs4tGF/K/z5O9I/2W5Yvrh7IuqNeYH+0Q==} + dependencies: + multiformats: 13.0.1 + dev: false /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} From f4911bbdeab2c36421c7bbc10cfd7a9247b15e36 Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 8 Feb 2024 21:52:33 +0000 Subject: [PATCH 109/110] All varsig tests passing --- .../varsig/src/__tests__/__vectors__/jws.car | Bin 10776 -> 10845 bytes ...p256k1.test.ts => eip712-fixtures.test.ts} | 0 ...jws-es256.test.ts => jws-fixtures.test.ts} | 48 ++++++++++++++---- 3 files changed, 37 insertions(+), 11 deletions(-) rename packages/varsig/src/__tests__/{eip712-secp256k1.test.ts => eip712-fixtures.test.ts} (100%) rename packages/varsig/src/__tests__/{jws-es256.test.ts => jws-fixtures.test.ts} (85%) diff --git a/packages/varsig/src/__tests__/__vectors__/jws.car b/packages/varsig/src/__tests__/__vectors__/jws.car index ddde7a8197a79ea1fd525f5e94f3ee8222c2212f..c0c44219c1cfe54ab3ca95a34c6d769ee73fa849 100644 GIT binary patch literal 10845 zcmds-dpy(oAIFJGI4LQIB&j$RF?Mn3q%mg0*v2+e9AuwCfX4z@AX8)r)OB028A33WMuq=8NB7b#Rh>9fW_YNngQ+Xo27xqPHZ}fs z15H?F*^15Hju?-)Z`?NBh~Cn4xe1N}i@CLx>G`Y1Y0!_2J9RFzDCUvYtokZ%Zj*)M z@f9s&cXM3*c5p<1P~-{nxL4;BWD8W8u#?RcvO^41G3jFl7f|r4ra9x6S(5!L@Af9H zp=1*8RMwUVoAUFUt+gLpSTDGo*e@%9S`FS4n`!HY0f1RP1H zaX7&=XCd7;Qg)pbNv9&h=wzG_7ZJ{3yWo&szAQ08N)&rZ(J%s846?BlDce;N85|)5 zy?H{=GZu%$Mc_GfUbw`cN{IH4fW@+C93Bus;?c1b*>`fm5SsNuH=2|qfpMfl7h5t3 zL1ht$EErOPqF^Nwrj#Y1#)c7XnaoIm7!`r%!Nph?wYY|0k z9-j>;NP&tTh+&3<>=z-jba{YaCtn5*7y`1GA{jj%Kr9Wdp6{(6ebr|de)F%TM(b3u zt;X7$&p2s5ZOgl|v+O~h87a9g{&aiMhR%+98-4Ykx-hrsb;;L}r&c)CzIdiu?Cv5e~C;B_^plDt-@>Y@85S^w7F^62r1pq>7Y zLi=GTsjKD%?KfWb+z&hD+g(xD-=~>My!^Z7G0OstV+<|Uf}&lYCIz)#01ycHf+!qs zC@~WbQ^+x4$)c7g`21g(;Gu~VJmOx!4r&Hka^ zOuUd-|3k{rr)w|uq9!$l(jBWb9{;wp9j!Ic*4p5JGj-6me4=d$HO^B-8tE!8^1 zrq>_dt*SEEizj`efF7pH@LYUUuR*0WVsCw?MuR&#R=?ipOl9|rfIg=TS9^VP^JIro z)yWf2=ce8}T0{cV)tnU|BV z`4)2u+ozbTiZ{e27yGU|Ai4V|2jmLD5aL(kn;aqXU;%u3G%ZpTH4ob-K(q38z{K@ zi+1O|P4>MqXG$S#{_HEm2HN9)(^Mvdqc=b0GPq;E^ZtR^iS65Yy{VYv4m()`m}3%s z(flR4DrR$3CZ>K}862HqDVM?N4~#PU=G_!^ly3^QJ+V4qb<0KA?+Yx(#O-;xZ1XH^ zm=dp0>3%U8{POrhWqfJ~-==ALmyTCC&d3QORNBS=R1#8g5mV=&nPr`QtM*;LE@#;I zQsEC&KfNz!W5{UJWuCV-rCD%i^BBZ|1!mQaV?H2{Tx!cQcvogqHls`Dq;;FcwDnhk z?(z4Zp>{d;SOI%qeE&u*_Bo2}=A1c@rKD~4rkZ4MUhg2G|9Pc)Xu(5E zabV~e^xodn=N|+d*z)OpIo*}yOC*2A?Lc2|7b+)KBEZ=oLi|Fk1$Y4pkB+gk5fOYS zTnW!ki184+14N$~Ukt_5nMtz2(s<66F;W834}~P#he!GN`CG~~&>q9)lDHn847!&y z27PuY%kS&rGQ<0_@7#R2@|yAFy?dS35rm2!N(4=6Fi8!} z-|m7p+HQta95uErJhl+~v~ejAJZ#X*s>D@oHwy z#jtTfl9wSJQ_Iy32V|>^h{;cP`$n6|n?pe`3Mjb^MkPf(KXMZ^Y7Zwh{79R=ev{VA zaA8*JR@k*8N=*i8y-GuI*p@eH0SQaSNv>lN%}v~GeRG^lEl>O*-u(yz1~THfIvMLU z-j67tFWNM~a;@Ps4IM`Eag!7gycw*1m{rXFDI0AYNh&yy)^pLR!(}xY@eMZ(nZSK) z8gf?8RwisZn2xvf99aJ50(M%U_Tp>IReqY~2ae2|U=0xUWb0r#~!u>*e_8D+OMJS(i0W_7u)@Lk}s@r`@K3ZnP zxpnPt&FGc89Vb2M%0DrXGHf{i&c3u3PVQ^HqF-Gm{`JD6?G4)7QY}jB(ADv_5o`97 zUOnWbJrZDvE^dz-{q0SR79}OVTrHvaoO!e1`UD*St;hhCJY*Q5ToOJAHp`HlaVHBy&YI0+^4q`9GxVzwR12ZfNjWAM>fPY;wUF&JUz zWn<$V1@p8;Q9*ZT9W7V@PFe%;9(0A{V7A5-DwFNj$XY#5JvJa{I74> zK06?=Tz5E4W8VP{*b$c)kEfe;V?Wescr6YjKZ-y!W%Uspw>SXjGQQ!aA+xWKO+(Hq zo4k-^G3AY3`H71a%b!nT@3Xi&ue6#MmyyUjelec|cZn^IJ$>Kz%dRNV&ziLf4sU)Mb~E`u`}%A=A!nN0zQTz09b1!a zpG4J@5Afjk1GDyQG)PF^s$O8fUBj7Ck^FV-Yjj4K+`dfjlXiP_mX5pOy-a1t?^}!w za{O)^2%l^;&81x{P03b1r2-Z1m$I*s!wD5(_PF7koGW4vHv1%wki)dGl$~t@WpB8^ z;JZ+OClY{waGl~D6Vy>GMIqElAVne6DGNm*)bnjcA=HCHMIqEvDn%jG?!TfCYTH#& z2(^c$D1_Q$Q4~Vm;3^8C?mZNRP^PCSgfbCDA=DU96hi$&K}E|?0RZ6;vj8hn_R58Y i0&E)u0%i{YY&hI5lEvild9uTO;h=QrfFCPNEc`FLTda)$ literal 10776 zcmds-c~Dd58ixg`yQM|h1Z7dIsGwm91YF7zLI@;=EemMOa*`afl7xf=87&AZE?f}` zcvZj>5i5&&5%j94EG~$E8wd(&tx-WyK`khH1939fw4DPTsU81Ha^{^(zWF_Q-{*Yi z^VkcB1OkcphnD$7yD@svM%I(oAh&zZs%h%r%25V46VYDc$bnx~?Be2tRPQHN?blgS zKqLkQe7#D&!E$!T)Z*FpMgOibxZ;|75H(X-^;>0Q>!w=!C8ui6C^F~e)2T5GNsNf^ z0mM>yLN4H;{tN( z@2&;Q3-%=Ee_O*^9hCiBQZ^-H}z0##>uE#%6 z9l_@C4O;?;C4nHH(=s2cUf`ICc?_zU!8F(N-K0Obfb8J#S_jg&NmGQl;99D)|3d0aNb2MrGjrOU7iyevrJ1;d8OKn5yY!9d0e zX*@BA{v>83=N*S)RAJQR0yb^SSXn*T;CXQZOkpEYj9w;<)HEl;us2 z=_&aPzJLK}Nr8$UkW#rI<0Vs_E*}u(3Dn?#Ops2MsOiA~a-x?(d=lIB_UYbR!ot*M z{mSF1R;zQyWk1`f|31~esMh3SS+x>{&dzL-c)aW{QuseAzt~am+Q_omB)}v;`Q-`k zrEGx+WPyAtSNofw($qj};$8~dPdYh^vgdVkoCkY1v4#1lvN!$Y-t_AFX=~0&Mg*NT5{UvS%@(&!)B zjt1t}G|v0Jngd6VZ)EMPbJ-KR@nvh(!xg89GemyfNAw-f=)epWy$;NS-G{0kK%WI> zsFV*6{hLo8eB0Ci-Hz@wWX7g~X*I=|H`&-}HoLxCwP#Dz{C4mDVZ$8oVTS6*jpEq+ zZRj`amu4Cx63F;Ht8MoEa7J8PzRQ*#NOMj*rsPier8QR+dj)V_{Dn=)Z9~Fv<6JBy z?_%7o@rz?9tp3_l1Be;(!t_Igws~}J;2Kg*&wftCvd~2ad zAOYwSfMLxBs0={#Ia)DP#%G2|nAg~Q(y8oFSyAWvbWX@on*pc!b&XR(96kG$VRibl zRl}y2T3oX-ov$N$P-=3EZ7XiWn|C5J&Z+ zNW262q0Ep_1rY;C1NkDVzZVl5h;fMa;qXWx0}b}+{p`Yq7w(YyO-!I-_$09{9O3 z%)`ts93Ng6(r`T3hR6M{DpSA39~-kP@|a{+Qf!3Ds}W`RiJK2tzHm6rCri{zXP~#( z6=zl?xW?iOB5m>+r?_jaPg|~9Z)Pze>5FbYY8U*_d^A9xRJK3BnRo7pnR?SAt%qs< zXkT-2cel9yp{-3{#LoC*IzU6kuLJZ5T=3Ha4VANCa5sX+KjhC3@Fg#^2^-&?XVSOg zWj!x@o;2F?BIvPy_nM2t<=KB|ma0AQ&qtre<90HUx3;KoPH%e9Yrl0aom*X<2gXhF zv7?4Y6Km2Q&icF_H#&1p<8c(9p5pT>Gvlrw$v=&IRbjELf!h52xH?ep?g?aiy}iE< zYEa31GLQT8(|w_ zRP$aJX5G^ZetWHCE3n||(X4ZsE#d*aoC$x9vSugGTye_^*0&qgxL^5O{~U|Rx2~?u zhN{&U-qCqR)iZ@{AKy}wk-?$(kk7c2M+sv4xr0Iu2kz$QlkB8>9qBG?KF9>w(W zsvXMm-Gt+t+Rl={xllUx%2}K8=c|J2rTK8X3Y)lvD^n9ii8>Ak6+M*0SszHZGnnY+ zGRbq_(XXTC{j4-=%vtcT*E7NL5glP%WN`NPiUt*yhRI!H2F@z489f;&a||q+i*BfE zIRN8#f$hd`r(c;@x-oe~On$skH`+|L4+XJlIp#=H=i!yDGmGjnRB4YkT`c*x`L~T< zB~LU8?!ACU=rkFq_3F&CVGG)*1+=fOvg4_&Y4S^>X~~Gami73|=4tZiYZH|Q-&Z6) z+eH{rK%cZ}faT7^X&RZ;gfnT4g@?Zm4Sn?!te%%LC80fgX=GpV6Utn-$1X>{;HIH= zuMbT_!|Dv%F&_JG-C+Qy&AolrqMHa z^{?j{3AK}y6?Sk|#f+|4vo5<;wNJJmH{7w;TwZeO^Xl2?jIbv|owM~4Tt92-H@9yc zpRRZ9=%c$EZksw=zqr+t*~WQTm0zLKp%E(FPo!sm0Zyn0HIwdn7$TsRbNj{1Zk40> zj3@0xf<^FT&!<+z18u$=Ea%=IHk^NEU(T18&fn?9%gsvR$y{H2JqPKfq(o-7+BbXZ z9h_A;-^4N@BIf3}0k`~>K79($+f8>8x6O!NYqj4*0YH=KfDljtn21m=XNA+YdZ2A;x&;}FilE@&JM#rLE5!&wA6hbF+&cv3H! z#=bV!NxMpW(+{k>+KX-`C7tK>gV%ksQ~!hSJM8m&`+^Ef!({on`}eG7rVy7bYmyqK>m{=FWQ~Wo8;OkG9(`T(J4t+k+9M-`n>DW(oq{EuVG0y6lM#t5DJFusVGE8ab;_A!_W) z0=Ou<(9(qdc7;cZ0-lT?Hmm<Y|%73$DiK4Y-iC$PnAs*{8ISQ@B) z!vO~0u>ihA1OnoJX^$X5oubkfLLK7K7D646&=x{Hn${LVJ>%0BLOo#87D8>@YYU;a zUA2W!dsx~+s67^KA=C}7wh-#xLt6-CdfGxL6VVnzjR9>T)ISu|w*2(~ARb~CpbOQn l92g5=IJvsQ5CFh%ad8*YseA!neJ+m+Du&MH(W7MI{{f;Wv}ynV diff --git a/packages/varsig/src/__tests__/eip712-secp256k1.test.ts b/packages/varsig/src/__tests__/eip712-fixtures.test.ts similarity index 100% rename from packages/varsig/src/__tests__/eip712-secp256k1.test.ts rename to packages/varsig/src/__tests__/eip712-fixtures.test.ts diff --git a/packages/varsig/src/__tests__/jws-es256.test.ts b/packages/varsig/src/__tests__/jws-fixtures.test.ts similarity index 85% rename from packages/varsig/src/__tests__/jws-es256.test.ts rename to packages/varsig/src/__tests__/jws-fixtures.test.ts index e7bce6be..7afd7868 100644 --- a/packages/varsig/src/__tests__/jws-es256.test.ts +++ b/packages/varsig/src/__tests__/jws-fixtures.test.ts @@ -22,7 +22,7 @@ describe('jws.car', () => { entries = root.entries as Array }) - // test('GenerateVectors', async () => { + // test.skip('GenerateVectors', async () => { // await createVectors() // }) @@ -77,7 +77,7 @@ describe('jws.car', () => { } }) - test.skip('Recover original from ipld node', async () => { + test('Recover original from ipld node', async () => { for (const entryCID of entries) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) @@ -89,16 +89,22 @@ describe('jws.car', () => { const node = car.get(entry.node) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const originalKlone = klona(originalExpected) - console.log('ori', originalKlone) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument if (entry.valid) { const originalRecovered = await toOriginal(node) - console.log('rec', originalRecovered) expect(originalRecovered).toEqual(originalKlone) } else { - console.log(entry) - expect(toOriginal(node)).rejects.toThrow() + if (entry.error === 'Invalid hash code') { + expect(toOriginal(node)).rejects.toThrow(/Hash type missmatch/) + } else if (entry.error === 'Missing alg in protected header') { + expect(toOriginal(node)).rejects.toThrow('Missing alg in protected header') + } else if (entry.error === 'Invalid signature') { + const originalRecovered = await toOriginal(node) + expect(originalRecovered).toEqual(originalKlone) + } else { + throw new Error('Unknown error') + } } } }) @@ -132,14 +138,14 @@ describe('jws.car', () => { // signature: ['es256', 'secp256k1', 'ed25519', 'ed448'] // }, { isRoot: true }) // -// await pipeline(car, fs.createWriteStream("./jws.car")); +// await pipeline(car, fs.createWriteStream("./src/__tests__/__vectors__/jws.car")); // // async function gen(name, opt, alg, crv) { // const kp = generateKeyPairSync(name, opt) // const {x, y } = kp.publicKey.export({ format: 'jwk' }) // const verificationKey = y ? // uint8arrays.concat([ -// [0x04], +// new Uint8Array([0x04]), // uint8arrays.fromString(x, 'base64url'), // uint8arrays.fromString(y, 'base64url') // ]) : @@ -162,17 +168,25 @@ describe('jws.car', () => { // // await expect(verify(node, verificationKey)).resolves.toEqual(true) // // console.log('passed one') // +// // contruct valid node // const entry1 = car.put({ // valid: true, // signer: { verificationKey }, // node: car.put(node), // original: car.put(jwt) // }) +// +// // contruct node with incorrect hash kind // const nodeKeccak = klona(node) // let tape = new BytesTape(nodeKeccak._sig) -// tape.read(1); tape.readVarint(); +// tape.read(1); +// let sigKind = tape.readVarint(); +// if (sigKind === MAGIC.SECP256K1) { +// // skip recovery bit for secp256k1 +// tape.read(1) +// } // const hashPosition = tape.position -// nodeKeccak._sig.set([MAGIC.KECCAK_256], hashPosition) // TODO - fix +// nodeKeccak._sig.set([MAGIC.KECCAK_256], hashPosition) // const entry2 = car.put({ // valid: false, // error: 'Invalid hash code', @@ -180,6 +194,8 @@ describe('jws.car', () => { // node: car.put(nodeKeccak), // original: car.put(jwt) // }) +// +// // contruct node with invalid signature // const jwtInvalid = jwt.substring(0, jwt.length - 10) + 'abc' + jwt.substring(jwt.length - 7) // const entry3 = car.put({ // valid: false, @@ -188,13 +204,22 @@ describe('jws.car', () => { // node: car.put(JWS.fromOriginal(jwtInvalid)), // original: car.put(jwtInvalid) // }) +// +// // construct node with missing alg // const invalidProtectedBytes = uint8arrays.fromString(JSON.stringify({})) // const invalidProtected = uint8arrays.toString(invalidProtectedBytes, 'base64url') // const jwtMissingAlg = invalidProtected + jwt.substring(jwt.indexOf('.')) // const nodeMissingAlg = klona(node) // const protectedLength = varintes.encode(invalidProtectedBytes.length)[0] // tape = new BytesTape(nodeKeccak._sig) -// tape.read(1); tape.readVarint(); tape.readVarint(); tape.readVarint(); +// tape.read(1); +// sigKind = tape.readVarint(); +// if (sigKind === MAGIC.SECP256K1) { +// // skip recovery bit for secp256k1 +// tape.read(1) +// } +// // skip hash and canon sigils +// tape.readVarint(); tape.readVarint(); // const protLenPos = tape.position // const klonLength = tape.readVarint() // const signature = nodeMissingAlg._sig.slice(tape.position + klonLength) @@ -204,6 +229,7 @@ describe('jws.car', () => { // invalidProtectedBytes, // signature // ]) +// console.log('nv', newVarsig) // nodeMissingAlg._sig = newVarsig // const entry4 = car.put({ // valid: false, From d9b24b0e06ebb00fdc5b1d4400cbb9373f3c907e Mon Sep 17 00:00:00 2001 From: Joel Thorstensson Date: Thu, 8 Feb 2024 22:08:10 +0000 Subject: [PATCH 110/110] eslint, plz don't mind my test --- .../src/__tests__/eip712-fixtures.test.ts | 7 +++++-- .../varsig/src/__tests__/jws-fixtures.test.ts | 20 +------------------ packages/varsig/src/canons/eip712.ts | 7 +++++-- packages/varsig/src/signing/ed25519.ts | 3 +-- packages/varsig/src/signing/p256.ts | 3 +-- 5 files changed, 13 insertions(+), 27 deletions(-) diff --git a/packages/varsig/src/__tests__/eip712-fixtures.test.ts b/packages/varsig/src/__tests__/eip712-fixtures.test.ts index e0bbef13..64720ae5 100644 --- a/packages/varsig/src/__tests__/eip712-fixtures.test.ts +++ b/packages/varsig/src/__tests__/eip712-fixtures.test.ts @@ -86,8 +86,11 @@ describe('eip712-secp256k1.car', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access originalKlone.signature = '0x' + - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - uint8arrays.toString(uint8arrays.concat([r, s, new Uint8Array([originalKlone.signature.v])]), 'hex') + uint8arrays.toString( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + uint8arrays.concat([r, s, new Uint8Array([originalKlone.signature.v])]), + 'hex', + ) } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument diff --git a/packages/varsig/src/__tests__/jws-fixtures.test.ts b/packages/varsig/src/__tests__/jws-fixtures.test.ts index 7afd7868..100d9fc1 100644 --- a/packages/varsig/src/__tests__/jws-fixtures.test.ts +++ b/packages/varsig/src/__tests__/jws-fixtures.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { readFile } from 'node:fs/promises' import { expect, test } from '@jest/globals' import { CARFactory, type CAR } from 'cartonne' @@ -15,10 +16,8 @@ describe('jws.car', () => { const carFilepath = new URL('./__vectors__/jws.car', import.meta.url) const carBytes = await readFile(carFilepath) car = factory.fromBytes(carBytes) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const root = car.get(car.roots[0]) if (!root) throw new Error(`Empty root`) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access entries = root.entries as Array }) @@ -28,22 +27,15 @@ describe('jws.car', () => { test('Verify signatures', async () => { for (const entryCID of entries) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access const node = car.get(entry.node) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const verificationKey = entry.signer.verificationKey - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (entry.valid) { - // eslint-disable-next-line jest/no-conditional-expect,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access await expect(verify(node, verificationKey)).resolves.toEqual(entry.valid) } else { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const verificationP = verify(node, verificationKey).catch(() => false) - // eslint-disable-next-line jest/no-conditional-expect await expect(verificationP).resolves.toEqual(false) } } @@ -51,13 +43,9 @@ describe('jws.car', () => { test('Create varsig ipld node', () => { for (const entryCID of entries) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access if (!entry.original) continue - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument const original = car.get(entry.original) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument const node = car.get(entry.node) if (entry.valid) { const varsigNode = JWS.fromOriginal(original) @@ -79,18 +67,12 @@ describe('jws.car', () => { test('Recover original from ipld node', async () => { for (const entryCID of entries) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const entry = car.get(entryCID) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access if (!entry.original) continue - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument const originalExpected = car.get(entry.original) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access const node = car.get(entry.node) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const originalKlone = klona(originalExpected) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument if (entry.valid) { const originalRecovered = await toOriginal(node) expect(originalRecovered).toEqual(originalKlone) diff --git a/packages/varsig/src/canons/eip712.ts b/packages/varsig/src/canons/eip712.ts index 6b1d86f9..decb8a86 100644 --- a/packages/varsig/src/canons/eip712.ts +++ b/packages/varsig/src/canons/eip712.ts @@ -166,7 +166,8 @@ export function prepareCanonicalization( hashing: HashingAlgo, keyType: SigningKind, ): CanonicalizationAlgo { - if (hashing.kind !== SUPPORTED_HASH_TYPE) throw new Error(`Unsupported hash type: ${hashing}`) + if (hashing.kind !== SUPPORTED_HASH_TYPE) + throw new Error(`Unsupported hash type: ${hashing.kind}`) if (!SUPPORTED_KEY_TYPES.includes(keyType)) throw new Error(`Unsupported key type: ${keyType}`) const metadataLength = tape.readVarint() const metadataBytes = tape.read(metadataLength) @@ -189,7 +190,9 @@ export function prepareCanonicalization( const original = (node: IpldNode, signature: Uint8Array, recoveryBit: number | undefined) => { const message = ipldNodeToMessage(node) - const sigBytes = recoveryBit ? uint8arrays.concat([signature, new Uint8Array([recoveryBit])]) : signature + const sigBytes = recoveryBit + ? uint8arrays.concat([signature, new Uint8Array([recoveryBit])]) + : signature const sigHex = `0x${uint8arrays.toString(sigBytes, 'base16')}` return { ...metadata, message, signature: sigHex } } diff --git a/packages/varsig/src/signing/ed25519.ts b/packages/varsig/src/signing/ed25519.ts index e8a71ff0..c14304e0 100644 --- a/packages/varsig/src/signing/ed25519.ts +++ b/packages/varsig/src/signing/ed25519.ts @@ -5,8 +5,7 @@ import type { BytesTape } from '../bytes-tape.js' const SIGIL = MAGIC.ED25519 -// @ts-ignore -function prepareVerifier(tape: BytesTape): SigningAlgo { +function prepareVerifier(_: BytesTape): SigningAlgo { return { kind: SIGIL, // eslint-disable-next-line @typescript-eslint/require-await diff --git a/packages/varsig/src/signing/p256.ts b/packages/varsig/src/signing/p256.ts index 192cbfc9..cf63200c 100644 --- a/packages/varsig/src/signing/p256.ts +++ b/packages/varsig/src/signing/p256.ts @@ -5,8 +5,7 @@ import type { BytesTape } from '../bytes-tape.js' const SIGIL = MAGIC.ES256 -// @ts-ignore -function prepareVerifier(tape: BytesTape): SigningAlgo { +function prepareVerifier(_: BytesTape): SigningAlgo { return { kind: SIGIL, // eslint-disable-next-line @typescript-eslint/require-await