Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SIP that reproduces the checksum algorithm #124

Merged
merged 7 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions SIPS/sip-19.md
Original file line number Diff line number Diff line change
@@ -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).
2 changes: 1 addition & 1 deletion SIPS/sip-8.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 10 additions & 0 deletions assets/sip-19/implementation/.editorconfig
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions assets/sip-19/implementation/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated
14 changes: 14 additions & 0 deletions assets/sip-19/implementation/.gitignore
Original file line number Diff line number Diff line change
@@ -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/
1 change: 1 addition & 0 deletions assets/sip-19/implementation/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
yarnPath: .yarn/releases/yarn-4.0.2.cjs
28 changes: 28 additions & 0 deletions assets/sip-19/implementation/implementation.ts
Original file line number Diff line number Diff line change
@@ -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))
)
)
);
}
14 changes: 14 additions & 0 deletions assets/sip-19/implementation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "implementation",
"packageManager": "[email protected]",
"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"
}
}
9 changes: 9 additions & 0 deletions assets/sip-19/implementation/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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. */
}
}
Loading
Loading