diff --git a/SIPS/sip-19.md b/SIPS/sip-19.md new file mode 100644 index 0000000..f6f73af --- /dev/null +++ b/SIPS/sip-19.md @@ -0,0 +1,54 @@ +--- +sip: 19 +title: Multi-file Snap checksum +status: Final +author: Olaf Tomalka (@ritave) +created: 2023-11-28 +--- + +## Abstract + +This SIP describes an algorithm used to checksum a snap in a reproducible manner in a way that includes all files required to run the snap. + +## Specification + +> Indented sections like this are considered non-normative. + +### Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and +"OPTIONAL" written in uppercase in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) + +### Steps + +#### Checksum `snap.manifest.json` + +> This algorithm works on `snap.manifest.json` version `0.1`. + +1. Parse `snap.manifest.json` into JavaScript object using `JSON.parse`. +2. Delete `.result.source.shasum` field from `snap.manifest.json`. +3. Convert rest of the structure back to JSON using [`fast-json-stable-stringify@^2.1.0`](https://www.npmjs.com/package/fast-json-stable-stringify) algorithm. +4. Checksum the resulting string using [auxiliary file](#checksum-auxiliary-files) algorithm. + +#### Auxiliary files + +1. Calculate [rfc4634 SHA-256](https://datatracker.ietf.org/doc/html/rfc4634) over the raw file data. + +#### Joining files + +1. Sort all the files by their paths. + 1. The sorting of paths is done using JavaScript's [Less Than over UTF-16 Code Units](https://tc39.es/ecma262/#sec-islessthan) + 2. Two files MUST NOT have the same path. +2. Calculate [SHA-256 checksum of each file separately](#checksum-auxiliary-files). +3. Concatenate all the checksums into one buffer and SHA-256 that buffer. +4. Encode the buffer using [RFC4648, Section 4: Base64 Encoding](https://datatracker.ietf.org/doc/html/rfc4648#section-4) algorithm. + +## Implementation + +- [Standardized implementation](../assets/sip-19/implementation/implementation.ts) +- [Live implementation in MetaMask](https://github.com/MetaMask/snaps/blob/6e0257741c7eb0fb71df5826fcfabb7658abd03e/packages/snaps-utils/src/snaps.ts#L175-L190) + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE). diff --git a/SIPS/sip-8.md b/SIPS/sip-8.md index f14363f..8ef9398 100644 --- a/SIPS/sip-8.md +++ b/SIPS/sip-8.md @@ -1,7 +1,7 @@ --- sip: 8 title: Snap Locations -status: Draft +status: Implementation discussions-to: https://github.com/MetaMask/SIPs/discussions/75 author: Olaf Tomalka (@ritave) created: 2022-11-03 diff --git a/assets/sip-19/implementation/.editorconfig b/assets/sip-19/implementation/.editorconfig new file mode 100644 index 0000000..1ed453a --- /dev/null +++ b/assets/sip-19/implementation/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,json,yml}] +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/assets/sip-19/implementation/.gitattributes b/assets/sip-19/implementation/.gitattributes new file mode 100644 index 0000000..af3ad12 --- /dev/null +++ b/assets/sip-19/implementation/.gitattributes @@ -0,0 +1,4 @@ +/.yarn/** linguist-vendored +/.yarn/releases/* binary +/.yarn/plugins/**/* binary +/.pnp.* binary linguist-generated diff --git a/assets/sip-19/implementation/.gitignore b/assets/sip-19/implementation/.gitignore new file mode 100644 index 0000000..c0a3513 --- /dev/null +++ b/assets/sip-19/implementation/.gitignore @@ -0,0 +1,14 @@ +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Swap the comments on the following lines if you wish to use zero-installs +# In that case, don't forget to run `yarn config set enableGlobalCache false`! +# Documentation here: https://yarnpkg.com/features/caching#zero-installs + +#!.yarn/cache +.pnp.* +node_modules/ diff --git a/assets/sip-19/implementation/.yarnrc.yml b/assets/sip-19/implementation/.yarnrc.yml new file mode 100644 index 0000000..ac9160a --- /dev/null +++ b/assets/sip-19/implementation/.yarnrc.yml @@ -0,0 +1 @@ +yarnPath: .yarn/releases/yarn-4.0.2.cjs diff --git a/assets/sip-19/implementation/implementation.ts b/assets/sip-19/implementation/implementation.ts new file mode 100644 index 0000000..bd9be1f --- /dev/null +++ b/assets/sip-19/implementation/implementation.ts @@ -0,0 +1,28 @@ +import stableStringify from "fast-json-stable-stringify"; +import { sha256 } from "@noble/hashes/sha256"; +import { concatBytes } from "@metamask/utils"; +import { base64 } from "@scure/base"; +import assert from "assert"; + +export type VFile = { path: string; contents: string }; + +function stableManifest({ path, contents }: VFile): VFile { + const structure = JSON.parse(contents); + delete structure.result.source.shasum; + return { path, contents: stableStringify(structure) }; +} + +export function checksumFiles(manifest: VFile, auxiliary: VFile[]): string { + return base64.encode( + sha256( + concatBytes( + [stableManifest(manifest), ...auxiliary] + .sort((a, b) => { + assert(a.path !== b.path, "Duplicate paths detected"); + return a.path < b.path ? -1 : 1; + }) + .map(({ contents }) => sha256(contents)) + ) + ) + ); +} diff --git a/assets/sip-19/implementation/package.json b/assets/sip-19/implementation/package.json new file mode 100644 index 0000000..335f74a --- /dev/null +++ b/assets/sip-19/implementation/package.json @@ -0,0 +1,14 @@ +{ + "name": "implementation", + "packageManager": "yarn@4.0.2", + "devDependencies": { + "@types/node": "^20.10.0", + "typescript": "^5.3.2" + }, + "dependencies": { + "@metamask/utils": "^8.2.1", + "@noble/hashes": "^1.3.2", + "@scure/base": "^1.1.3", + "fast-json-stable-stringify": "^2.1.0" + } +} diff --git a/assets/sip-19/implementation/tsconfig.json b/assets/sip-19/implementation/tsconfig.json new file mode 100644 index 0000000..f691083 --- /dev/null +++ b/assets/sip-19/implementation/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "module": "commonjs" /* Specify what module code is generated. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + "strict": true /* Enable all strict type-checking options. */, + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/assets/sip-19/implementation/yarn.lock b/assets/sip-19/implementation/yarn.lock new file mode 100644 index 0000000..5603d6d --- /dev/null +++ b/assets/sip-19/implementation/yarn.lock @@ -0,0 +1,275 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ethereumjs/common@npm:^3.2.0": + version: 3.2.0 + resolution: "@ethereumjs/common@npm:3.2.0" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + crc-32: "npm:^1.2.0" + checksum: 4e2256eb54cc544299f4d7ebc9daab7a3613c174de3981ea5ed84bd10c41a03d013d15b1abad292da62fd0c4b8ce5b220a258a25861ccffa32f2cc9a8a4b25d8 + languageName: node + linkType: hard + +"@ethereumjs/rlp@npm:^4.0.1": + version: 4.0.1 + resolution: "@ethereumjs/rlp@npm:4.0.1" + bin: + rlp: bin/rlp + checksum: 78379f288e9d88c584c2159c725c4a667a9742981d638bad760ed908263e0e36bdbd822c0a902003e0701195fd1cbde7adad621cd97fdfbf552c45e835ce022c + languageName: node + linkType: hard + +"@ethereumjs/tx@npm:^4.2.0": + version: 4.2.0 + resolution: "@ethereumjs/tx@npm:4.2.0" + dependencies: + "@ethereumjs/common": "npm:^3.2.0" + "@ethereumjs/rlp": "npm:^4.0.1" + "@ethereumjs/util": "npm:^8.1.0" + ethereum-cryptography: "npm:^2.0.0" + checksum: f168303edf5970673db06d2469a899632c64ba0cd5d24480e97683bd0e19cc22a7b0a7bc7db3a49760f09826d4c77bed89b65d65252daf54857dd3d97324fb9a + languageName: node + linkType: hard + +"@ethereumjs/util@npm:^8.1.0": + version: 8.1.0 + resolution: "@ethereumjs/util@npm:8.1.0" + dependencies: + "@ethereumjs/rlp": "npm:^4.0.1" + ethereum-cryptography: "npm:^2.0.0" + micro-ftch: "npm:^0.3.1" + checksum: 4e6e0449236f66b53782bab3b387108f0ddc050835bfe1381c67a7c038fea27cb85ab38851d98b700957022f0acb6e455ca0c634249cfcce1a116bad76500160 + languageName: node + linkType: hard + +"@metamask/utils@npm:^8.2.1": + version: 8.2.1 + resolution: "@metamask/utils@npm:8.2.1" + dependencies: + "@ethereumjs/tx": "npm:^4.2.0" + "@noble/hashes": "npm:^1.3.1" + "@scure/base": "npm:^1.1.3" + "@types/debug": "npm:^4.1.7" + debug: "npm:^4.3.4" + pony-cause: "npm:^2.1.10" + semver: "npm:^7.5.4" + superstruct: "npm:^1.0.3" + checksum: 7f6f02138f69f544dc7e27b52af995a630622c7e884bdf94f8c8ee78232a659a128c77088659f7ff9b030839fb52b14cc1655bdac85688ca435b46b5ecdbb844 + languageName: node + linkType: hard + +"@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": + version: 1.1.0 + resolution: "@noble/curves@npm:1.1.0" + dependencies: + "@noble/hashes": "npm:1.3.1" + checksum: 81115c3ebfa7e7da2d7e18d44d686f98dc6d35dbde3964412c05707c92d0994a01545bc265d5c0bc05c8c49333f75b99c9acef6750f5a79b3abcc8e0546acf88 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.3.1": + version: 1.3.1 + resolution: "@noble/hashes@npm:1.3.1" + checksum: 86512713aaf338bced594bc2046ab249fea4e1ba1e7f2ecd02151ef1b8536315e788c11608fafe1b56f04fad1aa3c602da7e5f8e5fcd5f8b0aa94435fe65278e + languageName: node + linkType: hard + +"@noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: 2482cce3bce6a596626f94ca296e21378e7a5d4c09597cbc46e65ffacc3d64c8df73111f2265444e36a3168208628258bbbaccba2ef24f65f58b2417638a20e7 + languageName: node + linkType: hard + +"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0": + version: 1.1.3 + resolution: "@scure/base@npm:1.1.3" + checksum: 4eb1d8b58da503ecdff743be36ae3562bbff724da82fb3401468d348659841ae4bb271aeae3a8cf6c4d06cd887dee3825ce6fdac2f699afc63838ae68c499baa + languageName: node + linkType: hard + +"@scure/bip32@npm:1.3.1": + version: 1.3.1 + resolution: "@scure/bip32@npm:1.3.1" + dependencies: + "@noble/curves": "npm:~1.1.0" + "@noble/hashes": "npm:~1.3.1" + "@scure/base": "npm:~1.1.0" + checksum: 9ff0ad56f512794aed1ed62e582bf855db829e688235420a116b210169dc31e3e2a8cc4a908126aaa07b6dcbcc4cd085eb12f9d0a8b507a88946d6171a437195 + languageName: node + linkType: hard + +"@scure/bip39@npm:1.2.1": + version: 1.2.1 + resolution: "@scure/bip39@npm:1.2.1" + dependencies: + "@noble/hashes": "npm:~1.3.0" + "@scure/base": "npm:~1.1.0" + checksum: fe951f69dd5a7cdcefbe865bce1b160d6b59ba19bd01d09f0718e54fce37a7d8be158b32f5455f0e9c426a7fbbede3e019bf0baa99bacc88ef26a76a07e115d4 + languageName: node + linkType: hard + +"@types/debug@npm:^4.1.7": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "npm:*" + checksum: 5dcd465edbb5a7f226e9a5efd1f399c6172407ef5840686b73e3608ce135eeca54ae8037dcd9f16bdb2768ac74925b820a8b9ecc588a58ca09eca6acabe33e2f + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 0.7.34 + resolution: "@types/ms@npm:0.7.34" + checksum: ac80bd90012116ceb2d188fde62d96830ca847823e8ca71255616bc73991aa7d9f057b8bfab79e8ee44ffefb031ddd1bcce63ea82f9e66f7c31ec02d2d823ccc + languageName: node + linkType: hard + +"@types/node@npm:^20.10.0": + version: 20.10.0 + resolution: "@types/node@npm:20.10.0" + dependencies: + undici-types: "npm:~5.26.4" + checksum: f379e57d9d28cb5f3d8eab943de0c54a0ca2f95ee356e1fe2a1a4fa718b740103ae522c50ce107cffd52c3642ef3244cfc55bf5369081dd6c48369c8587b21ae + languageName: node + linkType: hard + +"crc-32@npm:^1.2.0": + version: 1.2.2 + resolution: "crc-32@npm:1.2.2" + bin: + crc32: bin/crc32.njs + checksum: 11dcf4a2e77ee793835d49f2c028838eae58b44f50d1ff08394a610bfd817523f105d6ae4d9b5bef0aad45510f633eb23c903e9902e4409bed1ce70cb82b9bf0 + languageName: node + linkType: hard + +"debug@npm:^4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 + languageName: node + linkType: hard + +"ethereum-cryptography@npm:^2.0.0": + version: 2.1.2 + resolution: "ethereum-cryptography@npm:2.1.2" + dependencies: + "@noble/curves": "npm:1.1.0" + "@noble/hashes": "npm:1.3.1" + "@scure/bip32": "npm:1.3.1" + "@scure/bip39": "npm:1.2.1" + checksum: 784552709e3afd4ae9c606f3cf04ced49ab69f3864df58aca64f15317641470afd44573cbda821b9cf6781dac6dd3a95559fcc062299e23394094a3370387ec6 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"implementation@workspace:.": + version: 0.0.0-use.local + resolution: "implementation@workspace:." + dependencies: + "@metamask/utils": "npm:^8.2.1" + "@noble/hashes": "npm:^1.3.2" + "@scure/base": "npm:^1.1.3" + "@types/node": "npm:^20.10.0" + fast-json-stable-stringify: "npm:^2.1.0" + typescript: "npm:^5.3.2" + languageName: unknown + linkType: soft + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + +"micro-ftch@npm:^0.3.1": + version: 0.3.1 + resolution: "micro-ftch@npm:0.3.1" + checksum: b87d35a52aded13cf2daca8d4eaa84e218722b6f83c75ddd77d74f32cc62e699a672e338e1ee19ceae0de91d19cc24dcc1a7c7d78c81f51042fe55f01b196ed3 + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard + +"pony-cause@npm:^2.1.10": + version: 2.1.10 + resolution: "pony-cause@npm:2.1.10" + checksum: 55ad0ca52039895f273c69e55fc9fe882deff38689dc5962558bfa16cce0ea7cb5bb7b67d0c43ec9c3e7edeb81f81ee8c1113014930d77b2cbac5adc4ac7fb64 + languageName: node + linkType: hard + +"semver@npm:^7.5.4": + version: 7.5.4 + resolution: "semver@npm:7.5.4" + dependencies: + lru-cache: "npm:^6.0.0" + bin: + semver: bin/semver.js + checksum: 5160b06975a38b11c1ab55950cb5b8a23db78df88275d3d8a42ccf1f29e55112ac995b3a26a522c36e3b5f76b0445f1eef70d696b8c7862a2b4303d7b0e7609e + languageName: node + linkType: hard + +"superstruct@npm:^1.0.3": + version: 1.0.3 + resolution: "superstruct@npm:1.0.3" + checksum: 45ed9c41016641161a2ed93723d2cf6efc6fb2552ebb747b8df94cb73a37acd95288baad42c2d51ffe77956caf5c5200cd22622e166c6951777acd2fb11a7da5 + languageName: node + linkType: hard + +"typescript@npm:^5.3.2": + version: 5.3.2 + resolution: "typescript@npm:5.3.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: d7dbe1fbe19039e36a65468ea64b5d338c976550394ba576b7af9c68ed40c0bc5d12ecce390e4b94b287a09a71bd3229f19c2d5680611f35b7c53a3898791159 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.3.2#optional!builtin": + version: 5.3.2 + resolution: "typescript@patch:typescript@npm%3A5.3.2#optional!builtin::version=5.3.2&hash=e012d7" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 73c8bad74e732d93211c9d77f28b03307e2f5fc6a0afc73f4b783261ab567686a16d6ae958bdaef383a00be1b0b8c8b6741dd6ca3d13af4963fa7e47456d49c7 + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard