mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-utils.git
synced 2026-11-07 01:11:06 +00:00
Root signing page and a root chain check for replacing the root keys (#81)
- signed_config.py root drafts a later root in docs/root-signing/proposals/, where wallets never look, and keeps the current targets key by default. - docs/root-signing/: a page for the Pezkuwi Wallet DApp browser that shows the proposal and its fingerprint, has the holder's wallet sign <Bytes>payload</Bytes>, checks every signature as the wallet will, collects signature codes and offers root.N.json once two current and two new holders have signed. Crypto bundles are served beside it. - scripts/root/verify_root.mjs checks every published signed/root.N.json the way installed wallets do; Code Quality runs it. Wallet 1.3.0 accepts sr25519 only under the substrate signing context, and a root it cannot verify stops its config refresh, so such a root must never reach master. - Draft root 2: three new holder accounts, threshold 2, five years.
This commit is contained in:
1 parent
7d53e16b76
commit
6864b8c0aa
13 files changed
+27902
-8
No files matched your search
Generated
+562
@@ -0,0 +1,562 @@
|
||||
{
|
||||
"name": "pezkuwi-wallet-utils-root",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pezkuwi-wallet-utils-root",
|
||||
"dependencies": {
|
||||
"@pezkuwi/util": "14.0.26",
|
||||
"@pezkuwi/util-crypto": "14.0.26",
|
||||
"@polkadot/util": "13.5.9",
|
||||
"@polkadot/util-crypto": "13.5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz",
|
||||
"integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/networks": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/networks/-/networks-14.0.26.tgz",
|
||||
"integrity": "sha512-omDbJzFjDSm1FS32zebyNP1QGBk9/ZayM0YM0mUlOOwiLIP7iDVq3KQe2JdI7hm1dz01Lm68/hjerSoqiWEclA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/util": "14.0.26",
|
||||
"@substrate/ss58-registry": "^1.51.0",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/scure-sr25519": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/scure-sr25519/-/scure-sr25519-0.2.1.tgz",
|
||||
"integrity": "sha512-KGxCR+fI4p8QyIpwmRNIj8fbq7O+yz0iZZ/O2HJBVE7O9D6ksBKwIILqODJfE6Qvrf3F8DyT6/3cPoR3e/s8iA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/curves": "~1.9.2",
|
||||
"@noble/hashes": "~1.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/util": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/util/-/util-14.0.26.tgz",
|
||||
"integrity": "sha512-xV7uct4jtIqf+9LixcCoTgYz8JEZ2eK6aosMD92Zu5DRPSW87QcCYBQuq8Phec0LdjGvC/l7DgiloQODhTtHWA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/x-bigint": "14.0.26",
|
||||
"@pezkuwi/x-global": "14.0.26",
|
||||
"@pezkuwi/x-textdecoder": "14.0.26",
|
||||
"@pezkuwi/x-textencoder": "14.0.26",
|
||||
"@types/bn.js": "^5.1.6",
|
||||
"bn.js": "^5.2.1",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/util-crypto": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/util-crypto/-/util-crypto-14.0.26.tgz",
|
||||
"integrity": "sha512-YR+3N2FwKx3q9NF11q75O0lJ+SKpM9CNKPE7rLEoPvFt7wpHpB/GXOAekYhNtrotkcsI8v/WoyOi4RI9NiwYVQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@noble/curves": "^1.9.7",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@pezkuwi/networks": "14.0.26",
|
||||
"@pezkuwi/scure-sr25519": "^0.2.1",
|
||||
"@pezkuwi/util": "14.0.26",
|
||||
"@pezkuwi/wasm-crypto": "^7.5.20",
|
||||
"@pezkuwi/wasm-util": "^7.5.20",
|
||||
"@pezkuwi/x-bigint": "14.0.26",
|
||||
"@pezkuwi/x-randomvalues": "14.0.26",
|
||||
"@scure/base": "^1.1.7",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "14.0.26"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/wasm-bridge": {
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/wasm-bridge/-/wasm-bridge-7.5.21.tgz",
|
||||
"integrity": "sha512-KVWLXpmVHpYo1+woN+o8OK03Kk8+VwUtF/Dn5pnsVXbsGbXM22HWbx36ChzdkjpW35a5Ukfk92H0aCy8klfeDg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/wasm-util": "7.5.21",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "*",
|
||||
"@pezkuwi/x-randomvalues": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/wasm-crypto": {
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/wasm-crypto/-/wasm-crypto-7.5.21.tgz",
|
||||
"integrity": "sha512-gL771hQsC0NXPFTX4+yKTPEMfX1svRAM1lzep+LkniQcYqpqI4AAxzUaOGZYj5lQzXKSkgYpcaLuTZ441zPXAg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.3.3",
|
||||
"@pezkuwi/wasm-bridge": "7.5.21",
|
||||
"@pezkuwi/wasm-crypto-asmjs": "7.5.21",
|
||||
"@pezkuwi/wasm-crypto-init": "7.5.21",
|
||||
"@pezkuwi/wasm-crypto-wasm": "7.5.21",
|
||||
"@pezkuwi/wasm-util": "7.5.21",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "*",
|
||||
"@pezkuwi/x-randomvalues": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/wasm-crypto-asmjs": {
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.5.21.tgz",
|
||||
"integrity": "sha512-uqhAR3slp6qbn2Cf7g72v4Ko0hxWm/NuZL2ZDH65QBSGP9jkSnAoBjm5L7AO739quEVA8rFsR8CxN5gvzERspA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/wasm-crypto-init": {
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/wasm-crypto-init/-/wasm-crypto-init-7.5.21.tgz",
|
||||
"integrity": "sha512-pdgYqhsHIoxTdhwbxS8iacw4sFh3NLN9fJ3odzCAo4+QCr3xTU1FMjBiYJRkx6Q4BpthCjOWJPCwTEZnJ3lSCg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/wasm-bridge": "7.5.21",
|
||||
"@pezkuwi/wasm-crypto-asmjs": "7.5.21",
|
||||
"@pezkuwi/wasm-crypto-wasm": "7.5.21",
|
||||
"@pezkuwi/wasm-util": "7.5.21",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "*",
|
||||
"@pezkuwi/x-randomvalues": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/wasm-crypto-wasm": {
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/wasm-crypto-wasm/-/wasm-crypto-wasm-7.5.21.tgz",
|
||||
"integrity": "sha512-TR8hHkjY6oga4aI9Irlv/y00+KwiCBMt+EyXi3mTUVNn7qKE4DwXbDc/az4rLK8p5Kno4/iyCF6YXuHbjEK4Ag==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/wasm-util": "7.5.21",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/wasm-util": {
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/wasm-util/-/wasm-util-7.5.21.tgz",
|
||||
"integrity": "sha512-KMiUHCEa6iFNLNWyw9GUf5/K2UP6RjCia2iSGjcazwBNofAQVpP6zfeLb9y5szpextKGkDsODxpcIFenJ3KcFQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/x-bigint": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/x-bigint/-/x-bigint-14.0.26.tgz",
|
||||
"integrity": "sha512-TiCUVJiW07KurXot79AuKsqdIKGCtysMVDKLGlUc7mxlDJ+BCNJVmi9D0vvbB/gboxfJqONFQKt8eOXHKFg1iw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/x-global": "14.0.26",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/x-global": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/x-global/-/x-global-14.0.26.tgz",
|
||||
"integrity": "sha512-3D/dgFUugJfJ59JETey2EPpWL7X7NOrSEgNj1Rt3Vu9zR74QNocc5HEcyapFKj2hNNp5gPPDOOWbrLkkDnbdXw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/x-randomvalues": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/x-randomvalues/-/x-randomvalues-14.0.26.tgz",
|
||||
"integrity": "sha512-9tQ5+e7b5sC6y7NWFojfkfKZNSmWBpB1dnuyMv5vJMPlBPlsscTYARfk8ln860XWLdemQBYBvYbibmEBtLFfSQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/x-global": "14.0.26",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "14.0.26",
|
||||
"@pezkuwi/wasm-util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/x-textdecoder": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/x-textdecoder/-/x-textdecoder-14.0.26.tgz",
|
||||
"integrity": "sha512-O3l/pcT+7q89COMe+h+Gr+HLLym3k0RfWXWwEg6RYGBHxp77GfixwqAR/o6k6rBXNfxlAY60RcYUqu+DGkKSZw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/x-global": "14.0.26",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@pezkuwi/x-textencoder": {
|
||||
"version": "14.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@pezkuwi/x-textencoder/-/x-textencoder-14.0.26.tgz",
|
||||
"integrity": "sha512-aUk+h0g5iO+V3CZV1vdlRy4G9MbE0SbomB5Lw/8IkuQ+wNPK9JQ1FlArMhxVBfXM9JD8DPih/ViZsaNhWfjVVw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pezkuwi/x-global": "14.0.26",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/networks": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-13.5.9.tgz",
|
||||
"integrity": "sha512-nmKUKJjiLgcih0MkdlJNMnhEYdwEml2rv/h59ll2+rAvpsVWMTLCb6Cq6q7UC44+8kiWK2UUJMkFU+3PFFxndA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/util": "13.5.9",
|
||||
"@substrate/ss58-registry": "^1.51.0",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/util": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/util/-/util-13.5.9.tgz",
|
||||
"integrity": "sha512-pIK3XYXo7DKeFRkEBNYhf3GbCHg6dKQisSvdzZwuyzA6m7YxQq4DFw4IE464ve4Z7WsJFt3a6C9uII36hl9EWw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/x-bigint": "13.5.9",
|
||||
"@polkadot/x-global": "13.5.9",
|
||||
"@polkadot/x-textdecoder": "13.5.9",
|
||||
"@polkadot/x-textencoder": "13.5.9",
|
||||
"@types/bn.js": "^5.1.6",
|
||||
"bn.js": "^5.2.1",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/util-crypto": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz",
|
||||
"integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@noble/curves": "^1.3.0",
|
||||
"@noble/hashes": "^1.3.3",
|
||||
"@polkadot/networks": "13.5.9",
|
||||
"@polkadot/util": "13.5.9",
|
||||
"@polkadot/wasm-crypto": "^7.5.3",
|
||||
"@polkadot/wasm-util": "^7.5.3",
|
||||
"@polkadot/x-bigint": "13.5.9",
|
||||
"@polkadot/x-randomvalues": "13.5.9",
|
||||
"@scure/base": "^1.1.7",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "13.5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/wasm-bridge": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.5.4.tgz",
|
||||
"integrity": "sha512-6xaJVvoZbnbgpQYXNw9OHVNWjXmtcoPcWh7hlwx3NpfiLkkjljj99YS+XGZQlq7ks2fVCg7FbfknkNb8PldDaA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/wasm-util": "7.5.4",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "*",
|
||||
"@polkadot/x-randomvalues": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/wasm-crypto": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.5.4.tgz",
|
||||
"integrity": "sha512-1seyClxa7Jd7kQjfnCzTTTfYhTa/KUTDUaD3DMHBk5Q4ZUN1D1unJgX+v1aUeXSPxmzocdZETPJJRZjhVOqg9g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/wasm-bridge": "7.5.4",
|
||||
"@polkadot/wasm-crypto-asmjs": "7.5.4",
|
||||
"@polkadot/wasm-crypto-init": "7.5.4",
|
||||
"@polkadot/wasm-crypto-wasm": "7.5.4",
|
||||
"@polkadot/wasm-util": "7.5.4",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "*",
|
||||
"@polkadot/x-randomvalues": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/wasm-crypto-asmjs": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.5.4.tgz",
|
||||
"integrity": "sha512-ZYwxQHAJ8pPt6kYk9XFmyuFuSS+yirJLonvP+DYbxOrARRUHfN4nzp4zcZNXUuaFhpbDobDSFn6gYzye6BUotA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/wasm-crypto-init": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.5.4.tgz",
|
||||
"integrity": "sha512-U6s4Eo2rHs2n1iR01vTz/sOQ7eOnRPjaCsGWhPV+ZC/20hkVzwPAhiizu/IqMEol4tO2yiSheD4D6bn0KxUJhg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/wasm-bridge": "7.5.4",
|
||||
"@polkadot/wasm-crypto-asmjs": "7.5.4",
|
||||
"@polkadot/wasm-crypto-wasm": "7.5.4",
|
||||
"@polkadot/wasm-util": "7.5.4",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "*",
|
||||
"@polkadot/x-randomvalues": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/wasm-crypto-wasm": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.5.4.tgz",
|
||||
"integrity": "sha512-PsHgLsVTu43eprwSvUGnxybtOEuHPES6AbApcs7y5ZbM2PiDMzYbAjNul098xJK/CPtrxZ0ePDFnaQBmIJyTFw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/wasm-util": "7.5.4",
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/wasm-util": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.5.4.tgz",
|
||||
"integrity": "sha512-hqPpfhCpRAqCIn/CYbBluhh0TXmwkJnDRjxrU9Bnqtw9nMNa97D8JuOjdd2pi0rxm+eeLQ/f1rQMp71RMM9t4w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/x-bigint": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-13.5.9.tgz",
|
||||
"integrity": "sha512-JVW6vw3e8fkcRyN9eoc6JIl63MRxNQCP/tuLdHWZts1tcAYao0hpWUzteqJY93AgvmQ91KPsC1Kf3iuuZCi74g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/x-global": "13.5.9",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/x-global": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-13.5.9.tgz",
|
||||
"integrity": "sha512-zSRWvELHd3Q+bFkkI1h2cWIqLo1ETm+MxkNXLec3lB56iyq/MjWBxfXnAFFYFayvlEVneo7CLHcp+YTFd9aVSA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/x-randomvalues": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-13.5.9.tgz",
|
||||
"integrity": "sha512-Uuuz3oubf1JCCK97fsnVUnHvk4BGp/W91mQWJlgl5TIOUSSTIRr+lb5GurCfl4kgnQq53Zi5fJV+qR9YumbnZw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/x-global": "13.5.9",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/util": "13.5.9",
|
||||
"@polkadot/wasm-util": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/x-textdecoder": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-13.5.9.tgz",
|
||||
"integrity": "sha512-W2HhVNUbC/tuFdzNMbnXAWsIHSg9SC9QWDNmFD3nXdSzlXNgL8NmuiwN2fkYvCQBtp/XSoy0gDLx0C+Fo19cfw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/x-global": "13.5.9",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polkadot/x-textencoder": {
|
||||
"version": "13.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-13.5.9.tgz",
|
||||
"integrity": "sha512-SG0MHnLUgn1ZxFdm0KzMdTHJ47SfqFhdIPMcGA0Mg/jt2rwrfrP3jtEIJMsHfQpHvfsNPfv55XOMmoPWuQnP/Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@polkadot/x-global": "13.5.9",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
|
||||
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@substrate/ss58-registry": {
|
||||
"version": "1.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.51.0.tgz",
|
||||
"integrity": "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@types/bn.js": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz",
|
||||
"integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.6.tgz",
|
||||
"integrity": "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz",
|
||||
"integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz",
|
||||
"integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
|
||||
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "pezkuwi-wallet-utils-root",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Verifies the signed config root chain as the wallet does",
|
||||
"dependencies": {
|
||||
"@pezkuwi/util": "14.0.26",
|
||||
"@pezkuwi/util-crypto": "14.0.26",
|
||||
"@polkadot/util": "13.5.9",
|
||||
"@polkadot/util-crypto": "13.5.9"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Verifies the published root chain the way the wallet does, so a root that installed wallets would refuse
|
||||
// is never published: a root they cannot verify stops their config refresh.
|
||||
//
|
||||
// signed/root.json is the root the APK carries. Each signed/root.N.json after it must be the next version and
|
||||
// carry valid signatures from a threshold of the previous root's keys and of its own. An sr25519 key is a
|
||||
// wallet account and signs <Bytes>payload</Bytes>. Wallet 1.3.0 checks sr25519 under the "substrate" signing
|
||||
// context only, so that is what counts; a signature valid only under "bizinikiwi" is reported, not counted.
|
||||
//
|
||||
// node scripts/root/verify_root.mjs check the published chain
|
||||
// node scripts/root/verify_root.mjs FILE... also report the signatures on proposals
|
||||
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { cryptoWaitReady as polkadotReady, sr25519Verify as substrateVerify } from '@polkadot/util-crypto';
|
||||
import { cryptoWaitReady as pezkuwiReady, sr25519Verify as bizinikiwiVerify, ed25519Verify } from '@pezkuwi/util-crypto';
|
||||
|
||||
const REPO = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
const SIGNED = join(REPO, 'signed');
|
||||
|
||||
const sha256Hex = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
||||
const wrapBytes = (payload) => Buffer.concat([Buffer.from('<Bytes>'), payload, Buffer.from('</Bytes>')]);
|
||||
|
||||
function readEnvelope(path) {
|
||||
const envelope = JSON.parse(readFileSync(path, 'utf8'));
|
||||
const payload = Buffer.from(envelope.signed, 'base64');
|
||||
return { payload, root: JSON.parse(payload.toString('utf8')), signatures: envelope.signatures };
|
||||
}
|
||||
|
||||
function parseRoot(root) {
|
||||
if (root.type !== 'root') throw new Error(`type is ${root.type}, not root`);
|
||||
const keys = {};
|
||||
for (const [keyId, key] of Object.entries(root.keys)) {
|
||||
const bytes = Buffer.from(key.public, 'base64');
|
||||
if (bytes.length !== 32 || sha256Hex(bytes) !== keyId) throw new Error(`key ${keyId} does not match its id`);
|
||||
if (key.scheme !== 'sr25519' && key.scheme !== 'ed25519') throw new Error(`key ${keyId} has unknown scheme ${key.scheme}`);
|
||||
keys[keyId] = { scheme: key.scheme, bytes };
|
||||
}
|
||||
for (const role of ['root', 'targets']) {
|
||||
const { keyids, threshold } = root[role];
|
||||
if (threshold < 1 || threshold > keyids.length || !keyids.every((id) => id in keys)) throw new Error(`unusable ${role} role`);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// keyId -> "substrate" | "bizinikiwi" | "ed25519" | null for each signature on payload
|
||||
function classify(payload, signatures, keys) {
|
||||
const result = new Map();
|
||||
for (const { keyid, sig } of signatures) {
|
||||
const key = keys[keyid];
|
||||
if (!key) continue;
|
||||
const signature = Buffer.from(sig, 'base64');
|
||||
let kind = null;
|
||||
if (key.scheme === 'ed25519') {
|
||||
if (ed25519Verify(payload, signature, key.bytes)) kind = 'ed25519';
|
||||
} else if (substrateVerify(wrapBytes(payload), signature, key.bytes)) {
|
||||
kind = 'substrate';
|
||||
} else if (bizinikiwiVerify(wrapBytes(payload), signature, key.bytes)) {
|
||||
kind = 'bizinikiwi';
|
||||
}
|
||||
if (!result.get(keyid)) result.set(keyid, kind);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function count(classified, keyIds) {
|
||||
const accepted = keyIds.filter((id) => ['substrate', 'ed25519'].includes(classified.get(id)));
|
||||
const contextOnly = keyIds.filter((id) => classified.get(id) === 'bizinikiwi');
|
||||
return { accepted: accepted.length, contextOnly: contextOnly.length };
|
||||
}
|
||||
|
||||
function checkNext(previous, path) {
|
||||
const next = readEnvelope(path);
|
||||
const nextKeys = parseRoot(next.root);
|
||||
if (next.root.version !== previous.root.version + 1) {
|
||||
throw new Error(`version ${next.root.version} does not follow ${previous.root.version}`);
|
||||
}
|
||||
const lines = [];
|
||||
let ok = true;
|
||||
for (const [label, keys, role] of [
|
||||
['previous root', previous.keys, previous.root.root],
|
||||
['its own root', nextKeys, next.root.root],
|
||||
]) {
|
||||
const { accepted, contextOnly } = count(classify(next.payload, next.signatures, keys), role.keyids);
|
||||
const met = accepted >= role.threshold;
|
||||
ok &&= met;
|
||||
lines.push(` ${label}: ${accepted} of ${role.threshold} required${contextOnly ? `, ${contextOnly} valid only under bizinikiwi (not accepted by wallet 1.3.0)` : ''}${met ? '' : ' <- NOT MET'}`);
|
||||
}
|
||||
return { ok, lines, next: { root: next.root, keys: nextKeys } };
|
||||
}
|
||||
|
||||
await polkadotReady();
|
||||
await pezkuwiReady();
|
||||
|
||||
let failed = false;
|
||||
const first = readEnvelope(join(SIGNED, 'root.json'));
|
||||
let current = { root: first.root, keys: parseRoot(first.root) };
|
||||
console.log(`root ${current.root.version}: carried by the APK`);
|
||||
|
||||
for (let version = current.root.version + 1; existsSync(join(SIGNED, `root.${version}.json`)); version++) {
|
||||
const path = join(SIGNED, `root.${version}.json`);
|
||||
try {
|
||||
const { ok, lines, next } = checkNext(current, path);
|
||||
console.log(`root ${version}: ${ok ? 'accepted' : 'REFUSED'}`);
|
||||
lines.forEach((line) => console.log(line));
|
||||
if (!ok) {
|
||||
console.log(`::error::signed/root.${version}.json would be refused by installed wallets and stop their config refresh`);
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
current = next;
|
||||
} catch (e) {
|
||||
console.log(`::error::signed/root.${version}.json: ${e.message}`);
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const proposal of process.argv.slice(2)) {
|
||||
try {
|
||||
const { ok, lines } = checkNext(current, proposal);
|
||||
console.log(`proposal ${proposal}: ${ok ? 'ready to publish' : 'not ready'}`);
|
||||
lines.forEach((line) => console.log(line));
|
||||
} catch (e) {
|
||||
console.log(`proposal ${proposal}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(failed ? 1 : 0);
|
||||
@@ -9,7 +9,7 @@ Every metadata file is an envelope {"signed": base64(payload), "signatures": [{"
|
||||
hex sha256 of the 32-byte public key. An ed25519 key signs the payload bytes. An sr25519 key is a person's
|
||||
wallet account and signs through the wallet's message signing, which wraps the bytes as <Bytes>payload</Bytes>.
|
||||
|
||||
signed_config.py root --version N --expires-days D --root-threshold T --root ADDR... --targets-pub FILE
|
||||
signed_config.py root --version N --expires-days D --root-threshold T --root ADDR... [--targets-pub FILE]
|
||||
signed_config.py sign --key SEED_FILE [--expires-days D]
|
||||
signed_config.py check [--min-days D]
|
||||
"""
|
||||
@@ -33,10 +33,14 @@ from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
ROOT_FILE = REPO / "signed" / "root.json"
|
||||
TARGETS_FILE = REPO / "signed" / "targets.json"
|
||||
# A root after the first is drafted here, where the wallet never looks, and signed with the root signing page
|
||||
# beside it. Only a root with all its signatures moves to signed/: wallets that find a root they cannot verify
|
||||
# stop refreshing their config.
|
||||
PROPOSALS = REPO / "docs" / "root-signing" / "proposals"
|
||||
|
||||
# Everything the wallet may fetch from this repository is JSON outside these trees; the wallet
|
||||
# refuses any other JSON file from here, so a tree is only left out when nothing reads it.
|
||||
UNSERVED = ("nova-base/", "pezkuwi-overlay/", "docs/", "signed/")
|
||||
UNSERVED = ("nova-base/", "pezkuwi-overlay/", "docs/", "signed/", "scripts/")
|
||||
|
||||
BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
DAY = 86400
|
||||
@@ -117,7 +121,13 @@ def cmd_root(args):
|
||||
"public": base64.b64encode(public).decode(),
|
||||
}
|
||||
root_ids.append(key_id(public))
|
||||
targets_public = base64.b64decode(Path(args.targets_pub).read_text().strip())
|
||||
if args.targets_pub:
|
||||
targets_public = base64.b64decode(Path(args.targets_pub).read_text().strip())
|
||||
else:
|
||||
# Rotating the root keys keeps the targets key unless a new one is given.
|
||||
previous = current_root()
|
||||
(targets_id,) = previous["targets"]["keyids"]
|
||||
targets_public = base64.b64decode(previous["keys"][targets_id]["public"])
|
||||
if len(targets_public) != 32:
|
||||
sys.exit("targets public key must be 32 bytes")
|
||||
keys[key_id(targets_public)] = {
|
||||
@@ -135,11 +145,15 @@ def cmd_root(args):
|
||||
"root": {"keyids": sorted(root_ids), "threshold": args.root_threshold},
|
||||
"targets": {"keyids": [key_id(targets_public)], "threshold": 1},
|
||||
}
|
||||
path = root_file(args.version)
|
||||
path.parent.mkdir(exist_ok=True)
|
||||
# The first root is trusted because the APK carries it; a later one needs the signatures of a threshold
|
||||
# of the previous root keys and of its own, collected from the holders' wallets and added to this file
|
||||
# before it is published.
|
||||
# of the previous root keys and of its own, collected with the root signing page, before it is published.
|
||||
if args.version == 1:
|
||||
path = ROOT_FILE
|
||||
else:
|
||||
if args.version != current_root()["version"] + 1:
|
||||
sys.exit(f"the next root is version {current_root()['version'] + 1}")
|
||||
path = PROPOSALS / f"root.{args.version}.json"
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(envelope(canonical(payload), []))
|
||||
print(f"wrote {path.relative_to(REPO)} version {args.version}")
|
||||
|
||||
@@ -241,7 +255,7 @@ def main():
|
||||
root.add_argument("--expires-days", type=int, required=True)
|
||||
root.add_argument("--root-threshold", type=int, required=True)
|
||||
root.add_argument("--root", nargs="+", required=True)
|
||||
root.add_argument("--targets-pub", required=True)
|
||||
root.add_argument("--targets-pub")
|
||||
root.set_defaults(run=cmd_root)
|
||||
|
||||
sign = sub.add_parser("sign")
|
||||
|
||||
Reference in new issue
Block a user