diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index ab4e8ec..f558151 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -36,6 +36,7 @@ jobs: workspaces: | packages/wasm-utxo packages/wasm-bip32 + packages/wasm-solana cache-on-failure: true - name: Setup Node @@ -85,6 +86,8 @@ jobs: packages/wasm-utxo/js/wasm/ packages/wasm-bip32/dist/ packages/wasm-bip32/js/wasm/ + packages/wasm-solana/dist/ + packages/wasm-solana/js/wasm/ retention-days: 1 test: @@ -94,7 +97,7 @@ jobs: strategy: fail-fast: false matrix: - package: [wasm-bip32, wasm-utxo] + package: [wasm-bip32, wasm-utxo, wasm-solana] include: - package: wasm-utxo needs-wasm-pack: true @@ -102,6 +105,9 @@ jobs: - package: wasm-bip32 needs-wasm-pack: false has-wasm-pack-tests: false + - package: wasm-solana + needs-wasm-pack: false + has-wasm-pack-tests: false steps: - uses: actions/checkout@v4 with: diff --git a/package-lock.json b/package-lock.json index 88d599b..6bbcfe9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,15 +122,17 @@ "license": "ISC" }, "node_modules/@bitgo/secp256k1": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@bitgo/secp256k1/-/secp256k1-1.8.0.tgz", - "integrity": "sha512-sdVLB9qtrgL9Yi0vmCQIbeGZcTXhMwoadHEWZd1gka9Z0n3G4sdwxR+P2d2vFbgNbAJFt/9k8b1WOX9RFZ5e4w==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@bitgo/secp256k1/-/secp256k1-1.9.0.tgz", + "integrity": "sha512-1iwJ4BnG8WovUcWkjIR2n+9IgfOz07jKThvpWxuq6HIUsjoUDkMndrpcIrs+4c+CmPUWb74u4rVCo13EdW3zPw==", "dev": true, "license": "MIT", "dependencies": { "@brandonblack/musig": "^0.0.1-alpha.0", "@noble/secp256k1": "1.6.3", "bip32": "^3.0.1", + "bitcoinjs-message": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.3", + "bs58check": "^2.1.2", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "ecpair": "npm:@bitgo/ecpair@2.1.0-rc.0" @@ -175,6 +177,10 @@ "resolved": "packages/wasm-bip32", "link": true }, + "node_modules/@bitgo/wasm-solana": { + "resolved": "packages/wasm-solana", + "link": true + }, "node_modules/@bitgo/wasm-utxo": { "resolved": "packages/wasm-utxo", "link": true @@ -2954,6 +2960,7 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -3332,6 +3339,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -3562,6 +3570,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4662,6 +4671,7 @@ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", @@ -5137,6 +5147,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5199,6 +5210,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5643,6 +5655,32 @@ "node": ">=8.0.0" } }, + "node_modules/bitcoinjs-message": { + "name": "@bitgo-forks/bitcoinjs-message", + "version": "1.0.0-master.3", + "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.3.tgz", + "integrity": "sha512-mWMXFSb9pTcbxcvU4cQGkickuhPDnpadHs6eUK6F07pJZ42O4eA3j0anwfTsfpqs8UpSzM8UtrUEG4ao5+/yZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "5.0.1", + "varuint-bitcoin": "^1.0.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/bitcoinjs-message/node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -5824,6 +5862,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001646", "electron-to-chromium": "^1.5.4", @@ -5859,6 +5898,16 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/buffer-equals": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz", + "integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7966,6 +8015,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8808,7 +8858,8 @@ "node_modules/fp-ts": { "version": "2.16.9", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.9.tgz", - "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==" + "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==", + "peer": true }, "node_modules/fresh": { "version": "0.5.2", @@ -10250,6 +10301,7 @@ "version": "2.2.21", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.21.tgz", "integrity": "sha512-zz2Z69v9ZIC3mMLYWIeoUcwWD6f+O7yP92FMVVaXEOSZH1jnVBmET/urd/uoarD1WGBY4rCj8TAyMPzsGNzMFQ==", + "peer": true, "peerDependencies": { "fp-ts": "^2.5.0" } @@ -11748,6 +11800,7 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -12514,6 +12567,7 @@ "version": "2.3.13", "resolved": "https://registry.npmjs.org/monocle-ts/-/monocle-ts-2.3.13.tgz", "integrity": "sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==", + "peer": true, "peerDependencies": { "fp-ts": "^2.5.0" } @@ -12817,6 +12871,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/newtype-ts/-/newtype-ts-0.3.5.tgz", "integrity": "sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g==", + "peer": true, "peerDependencies": { "fp-ts": "^2.0.0", "monocle-ts": "^2.0.0" @@ -12832,6 +12887,13 @@ "tslib": "^2.0.3" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "dev": true, + "license": "MIT" + }, "node_modules/node-emoji": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", @@ -12882,6 +12944,18 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/@npmcli/agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", @@ -15222,6 +15296,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -15373,6 +15448,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -16679,6 +16755,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.1", @@ -17764,6 +17841,22 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/secp256k1": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.1.tgz", + "integrity": "sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -17789,6 +17882,7 @@ "integrity": "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -19901,6 +19995,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -20083,7 +20178,8 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/tsx": { "version": "4.20.6", @@ -20236,6 +20332,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20604,6 +20701,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -20650,6 +20748,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -20733,6 +20832,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -20845,6 +20945,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -21333,14 +21434,14 @@ } }, "packages/wasm-bip32/node_modules/@bitgo/utxo-lib": { - "version": "11.19.0", - "resolved": "https://registry.npmjs.org/@bitgo/utxo-lib/-/utxo-lib-11.19.0.tgz", - "integrity": "sha512-VOH+n3YXQnce7EevIrs69D5uNP2JWbSpJ4/C9Tpg0Lu4TF0JTw732rkMy17uFWiYPkpycRji179hnhTCk11g7A==", + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@bitgo/utxo-lib/-/utxo-lib-11.19.1.tgz", + "integrity": "sha512-w6gYqVOMkGHo2U00MsLuiZ4DzKrQ/69emgdypbjZtKGvn331ty4/S4ZEubBAGG6TcXFhzusWOJfg1ra2EP/T/A==", "dev": true, "license": "MIT", "dependencies": { "@bitgo/blake2b": "^3.2.4", - "@bitgo/secp256k1": "^1.8.0", + "@bitgo/secp256k1": "^1.9.0", "@brandonblack/musig": "^0.0.1-alpha.0", "bech32": "^2.0.0", "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", @@ -21380,6 +21481,21 @@ "node": ">=8.0.0" } }, + "packages/wasm-solana": { + "name": "@bitgo/wasm-solana", + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/mocha": "^10.0.7", + "@types/node": "^22.10.5", + "eslint": "^9.17.0", + "mocha": "^10.6.0", + "tsx": "4.20.6", + "typescript": "^5.5.3", + "typescript-eslint": "^8.18.2" + } + }, "packages/wasm-utxo": { "name": "@bitgo/wasm-utxo", "version": "0.0.2", diff --git a/packages/wasm-solana/.gitignore b/packages/wasm-solana/.gitignore new file mode 100644 index 0000000..f2d18aa --- /dev/null +++ b/packages/wasm-solana/.gitignore @@ -0,0 +1,10 @@ +target/ +node_modules/ +# we actually only track the .ts files +dist/ +test/*.js +test/*.d.ts +js/*.js +js/*.d.ts +js/wasm +.vscode diff --git a/packages/wasm-solana/.mocharc.json b/packages/wasm-solana/.mocharc.json new file mode 100644 index 0000000..f585fb0 --- /dev/null +++ b/packages/wasm-solana/.mocharc.json @@ -0,0 +1,5 @@ +{ + "extensions": ["ts", "tsx", "js", "jsx"], + "spec": ["test/**/*.ts"], + "node-option": ["import=tsx/esm", "experimental-wasm-modules"] +} diff --git a/packages/wasm-solana/.prettierignore b/packages/wasm-solana/.prettierignore new file mode 100644 index 0000000..05161f9 --- /dev/null +++ b/packages/wasm-solana/.prettierignore @@ -0,0 +1,5 @@ +dist/ +target/ +js/wasm/ +pkg/ +node_modules/ diff --git a/packages/wasm-solana/Cargo.lock b/packages/wasm-solana/Cargo.lock new file mode 100644 index 0000000..27ddb0a --- /dev/null +++ b/packages/wasm-solana/Cargo.lock @@ -0,0 +1,1060 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rand_core 0.6.4", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519 1.5.3", + "rand", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519 2.2.3", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "five8" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b8549488b4715defcb0d8a8a1c1c76a80661b5fa106b4ca0e7fce59d7d875" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_const" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "solana-atomic-u64" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52e52720efe60465b052b9e7445a01c17550666beec855cce66f44766697bc2" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-decode-error" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c781686a18db2f942e70913f7ca15dc120ec38dcab42ff7557db2c70c625a35" +dependencies = [ + "num-traits", +] + +[[package]] +name = "solana-define-syscall" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2" + +[[package]] +name = "solana-hash" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b96e9f0300fa287b545613f007dfe20043d7812bee255f418c1eb649c93b63" +dependencies = [ + "five8", + "js-sys", + "solana-atomic-u64", + "solana-sanitize", + "wasm-bindgen", +] + +[[package]] +name = "solana-instruction" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab5682934bd1f65f8d2c16f21cb532526fcc1a09f796e2cacdb091eee5774ad" +dependencies = [ + "getrandom 0.2.17", + "js-sys", + "num-traits", + "solana-define-syscall", + "solana-pubkey", + "wasm-bindgen", +] + +[[package]] +name = "solana-keypair" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd3f04aa1a05c535e93e121a95f66e7dcccf57e007282e8255535d24bf1e98bb" +dependencies = [ + "ed25519-dalek 1.0.1", + "five8", + "rand", + "solana-pubkey", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "wasm-bindgen", +] + +[[package]] +name = "solana-pubkey" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b62adb9c3261a052ca1f999398c388f1daf558a1b492f60a6d9e64857db4ff1" +dependencies = [ + "curve25519-dalek 4.1.3", + "five8", + "five8_const", + "getrandom 0.2.17", + "js-sys", + "num-traits", + "solana-atomic-u64", + "solana-decode-error", + "solana-define-syscall", + "solana-sanitize", + "solana-sha256-hasher", + "wasm-bindgen", +] + +[[package]] +name = "solana-sanitize" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" + +[[package]] +name = "solana-seed-phrase" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" +dependencies = [ + "hmac", + "pbkdf2", + "sha2 0.10.9", +] + +[[package]] +name = "solana-sha256-hasher" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa3feb32c28765f6aa1ce8f3feac30936f16c5c3f7eb73d63a5b8f6f8ecdc44" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall", + "solana-hash", +] + +[[package]] +name = "solana-signature" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c8ec8e657aecfc187522fc67495142c12f35e55ddeca8698edbb738b8dbd8c" +dependencies = [ + "ed25519-dalek 1.0.1", + "five8", + "solana-sanitize", +] + +[[package]] +name = "solana-signer" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" +dependencies = [ + "solana-pubkey", + "solana-signature", + "solana-transaction-error", +] + +[[package]] +name = "solana-transaction-error" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" +dependencies = [ + "solana-instruction", + "solana-sanitize", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasm-solana" +version = "0.1.0" +dependencies = [ + "ed25519-dalek 2.2.0", + "hex", + "js-sys", + "serde", + "serde_json", + "solana-keypair", + "solana-pubkey", + "solana-signer", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/packages/wasm-solana/Cargo.toml b/packages/wasm-solana/Cargo.toml new file mode 100644 index 0000000..3cb7dad --- /dev/null +++ b/packages/wasm-solana/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "wasm-solana" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[lints.clippy] +all = "warn" + +[dependencies] +wasm-bindgen = "0.2" +js-sys = "0.3" +# Solana SDK crates +solana-pubkey = { version = "2.0", features = ["curve25519"] } +solana-keypair = "2.0" +solana-signer = "2.0" +# Ed25519 for deriving pubkey from 32-byte seed (solana-keypair expects 64-byte format) +ed25519-dalek = { version = "2.1", default-features = false, features = ["std"] } + +[dev-dependencies] +wasm-bindgen-test = "0.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +hex = "0.4" + +[profile.release] +strip = true diff --git a/packages/wasm-solana/Makefile b/packages/wasm-solana/Makefile new file mode 100644 index 0000000..2e881e6 --- /dev/null +++ b/packages/wasm-solana/Makefile @@ -0,0 +1,70 @@ +WASM_PACK = wasm-pack +WASM_OPT = wasm-opt +WASM_PACK_FLAGS = --no-pack --weak-refs + +ifdef WASM_PACK_DEV + WASM_PACK_FLAGS += --dev +endif + +# Auto-detect Mac and use Homebrew LLVM for WASM compilation +# Apple's Clang doesn't support wasm32-unknown-unknown target +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Darwin) + # Mac detected - check for Homebrew LLVM installation + HOMEBREW_LLVM := $(shell brew --prefix llvm 2>/dev/null) + + ifdef HOMEBREW_LLVM + export CC = $(HOMEBREW_LLVM)/bin/clang + export AR = $(HOMEBREW_LLVM)/bin/llvm-ar + $(info Using Homebrew LLVM: $(HOMEBREW_LLVM)) + else + $(warning Homebrew LLVM not found. Install with: brew install llvm) + $(warning Continuing with system clang - may fail on Apple Silicon) + endif +endif + +define WASM_PACK_COMMAND + $(WASM_PACK) build --no-opt --out-dir $(1) $(WASM_PACK_FLAGS) --target $(2) +endef + +# run wasm-opt separately so we can pass `--enable-bulk-memory` +define WASM_OPT_COMMAND + $(WASM_OPT) --enable-bulk-memory --enable-nontrapping-float-to-int --enable-sign-ext -Oz $(1)/*.wasm -o $(1)/*.wasm +endef + +define REMOVE_GITIGNORE + find $(1) -name .gitignore -delete +endef + +define SHOW_WASM_SIZE + @find $(1) -name "*.wasm" -exec gzip -k {} \; + @find $(1) -name "*.wasm" -exec du -h {} \; + @find $(1) -name "*.wasm.gz" -exec du -h {} \; + @find $(1) -name "*.wasm.gz" -delete +endef + +define BUILD + rm -rf $(1) + $(call WASM_PACK_COMMAND,$(1),$(2)) + $(call WASM_OPT_COMMAND,$(1)) + $(call REMOVE_GITIGNORE,$(1)) + $(call SHOW_WASM_SIZE,$(1)) +endef + +.PHONY: js/wasm +js/wasm: + $(call BUILD,$@,bundler) + +.PHONY: dist/esm/js/wasm +dist/esm/js/wasm: + $(call BUILD,$@,bundler) + +.PHONY: dist/cjs/js/wasm +dist/cjs/js/wasm: + $(call BUILD,$@,nodejs) + +.PHONY: lint +lint: + cargo fmt --check + cargo clippy --all-targets --all-features -- -D warnings diff --git a/packages/wasm-solana/README.md b/packages/wasm-solana/README.md new file mode 100644 index 0000000..b93b89d --- /dev/null +++ b/packages/wasm-solana/README.md @@ -0,0 +1,23 @@ +# wasm-solana + +WASM bindings for Solana cryptographic operations. This package provides Rust-based bindings for Ed25519 keypair generation, public key operations, and signature verification for Solana. + +## Building + +### Mac + +Requires Homebrew LLVM (Apple's Clang doesn't support WASM targets): + +```bash +brew install llvm +npm run build +``` + +### Docker (optional) + +If you prefer a containerized build environment: + +```bash +make -f Container.mk build-image +make -f Container.mk build-wasm +``` diff --git a/packages/wasm-solana/eslint.config.js b/packages/wasm-solana/eslint.config.js new file mode 100644 index 0000000..a94acbe --- /dev/null +++ b/packages/wasm-solana/eslint.config.js @@ -0,0 +1,18 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + ignores: ["dist/", "pkg/", "target/", "node_modules/", "js/wasm/", "*.config.js"], + }, +); diff --git a/packages/wasm-solana/js/README.md b/packages/wasm-solana/js/README.md new file mode 100644 index 0000000..f3d7c55 --- /dev/null +++ b/packages/wasm-solana/js/README.md @@ -0,0 +1,95 @@ +# Purpose + +This directory provides TypeScript wrappers over the WASM bindings generated by `wasm-pack`. + +While `wasm-bindgen` generates usable TypeScript types, this wrapper layer provides: + +- Better TypeScript signatures with precise types +- `camelCase` method names (JavaScript convention) wrapping `snake_case` WASM methods (Rust convention) +- Encapsulation of WASM instances with private constructors + +## Architecture + +This package follows the **Class Wrapper Pattern**. + +### Class Wrapper Pattern + +Used for stateful objects that maintain WASM instances (e.g., `Keypair`, `Pubkey`). + +**Characteristics:** + +- Private `_wasm` property holds the underlying WASM instance +- Private constructor prevents direct instantiation +- Static factory methods (camelCase) for object creation +- Instance methods (camelCase) wrap WASM methods +- Public `wasm` getter for internal access (marked `@internal`) + +### Example + +Given a WASM-generated class: + +```typescript +// wasm/wasm_solana.d.ts (generated by wasm-bindgen) +export class WasmKeypair { + static from_secret_key(secret_key: Uint8Array): WasmKeypair; + readonly public_key: Uint8Array; + address(): string; +} +``` + +We create a wrapper class: + +```typescript +// keypair.ts +import { WasmKeypair } from "./wasm/wasm_solana.js"; + +export class Keypair { + private constructor(private _wasm: WasmKeypair) {} + + // Static factory method (camelCase) + static fromSecretKey(secretKey: Uint8Array): Keypair { + const wasm = WasmKeypair.from_secret_key(secretKey); + return new Keypair(wasm); + } + + // Property getter (camelCase) accesses snake_case WASM property + get publicKey(): Uint8Array { + return this._wasm.public_key; + } + + // Instance method (camelCase) calls snake_case WASM method + getAddress(): string { + return this._wasm.address(); + } + + /** @internal */ + get wasm(): WasmKeypair { + return this._wasm; + } +} +``` + +### Main Entry Point + +The `index.ts` file exports: + +- Namespace exports for explicit imports (`export * as keypair`) +- Top-level class exports for convenience (`export { Keypair }`) + +```typescript +// index.ts +export * as keypair from "./keypair.js"; +export * as pubkey from "./pubkey.js"; + +export { Keypair } from "./keypair.js"; +export { Pubkey } from "./pubkey.js"; +``` + +## Naming Conventions + +| Layer | Convention | Example | +| ---------- | ------------ | ------------------------------- | +| Rust WASM | `snake_case` | `from_secret_key`, `public_key` | +| TypeScript | `camelCase` | `fromSecretKey`, `publicKey` | + +**Important:** Rust exports use `snake_case` with no `js_name` overrides. TypeScript wrappers handle the conversion to `camelCase`. diff --git a/packages/wasm-solana/js/index.ts b/packages/wasm-solana/js/index.ts new file mode 100644 index 0000000..217c524 --- /dev/null +++ b/packages/wasm-solana/js/index.ts @@ -0,0 +1,12 @@ +import * as wasm from "./wasm/wasm_solana.js"; + +// Force webpack to include the WASM module +void wasm; + +// Namespace exports for explicit imports +export * as keypair from "./keypair.js"; +export * as pubkey from "./pubkey.js"; + +// Top-level class exports for convenience +export { Keypair } from "./keypair.js"; +export { Pubkey } from "./pubkey.js"; diff --git a/packages/wasm-solana/js/keypair.ts b/packages/wasm-solana/js/keypair.ts new file mode 100644 index 0000000..3ed3e68 --- /dev/null +++ b/packages/wasm-solana/js/keypair.ts @@ -0,0 +1,70 @@ +import { WasmKeypair } from "./wasm/wasm_solana.js"; + +/** + * Solana Ed25519 Keypair for address generation and signing + * + * A keypair consists of a 32-byte secret key and a 32-byte public key. + * The public key (base58-encoded) is the Solana address. + */ +export class Keypair { + private constructor(private _wasm: WasmKeypair) {} + + /** + * Create a keypair from a 32-byte secret key + * @param secretKey - The 32-byte Ed25519 secret key + * @returns A Keypair instance + */ + static fromSecretKey(secretKey: Uint8Array): Keypair { + const wasm = WasmKeypair.from_secret_key(secretKey); + return new Keypair(wasm); + } + + /** + * Create a keypair from a 64-byte Solana secret key (secret + public concatenated) + * This is the format used by @solana/web3.js Keypair.fromSecretKey() + * @param secretKey - The 64-byte Solana secret key + * @returns A Keypair instance + */ + static fromSolanaSecretKey(secretKey: Uint8Array): Keypair { + const wasm = WasmKeypair.from_solana_secret_key(secretKey); + return new Keypair(wasm); + } + + /** + * Get the public key as a 32-byte Uint8Array + */ + get publicKey(): Uint8Array { + return this._wasm.public_key; + } + + /** + * Get the secret key as a 32-byte Uint8Array + */ + get secretKey(): Uint8Array { + return this._wasm.secret_key; + } + + /** + * Get the Solana address (base58-encoded public key) + * @returns The address as a base58 string + */ + getAddress(): string { + return this._wasm.address(); + } + + /** + * Get the public key as a base58 string + * @returns The public key as a base58 string + */ + toBase58(): string { + return this._wasm.to_base58(); + } + + /** + * Get the underlying WASM instance (internal use only) + * @internal + */ + get wasm(): WasmKeypair { + return this._wasm; + } +} diff --git a/packages/wasm-solana/js/pubkey.ts b/packages/wasm-solana/js/pubkey.ts new file mode 100644 index 0000000..03feee0 --- /dev/null +++ b/packages/wasm-solana/js/pubkey.ts @@ -0,0 +1,77 @@ +import { WasmPubkey } from "./wasm/wasm_solana.js"; + +/** + * Solana public key (address) + * + * A Solana address is a 32-byte Ed25519 public key, typically represented as a base58 string. + */ +export class Pubkey { + private constructor(private _wasm: WasmPubkey) {} + + /** + * Create a Pubkey from a base58 string + * @param address - The base58-encoded address + * @returns A Pubkey instance + */ + static fromBase58(address: string): Pubkey { + const wasm = WasmPubkey.from_base58(address); + return new Pubkey(wasm); + } + + /** + * Create a Pubkey from raw bytes (32 bytes) + * @param bytes - The 32-byte public key + * @returns A Pubkey instance + */ + static fromBytes(bytes: Uint8Array): Pubkey { + const wasm = WasmPubkey.from_bytes(bytes); + return new Pubkey(wasm); + } + + /** + * Convert to base58 string (the standard Solana address format) + * @returns The address as a base58 string + */ + toBase58(): string { + return this._wasm.to_base58(); + } + + /** + * Get as raw bytes (32 bytes) + * @returns The public key as a Uint8Array + */ + toBytes(): Uint8Array { + return this._wasm.to_bytes(); + } + + /** + * Check if two pubkeys are equal + * @param other - The other Pubkey to compare + * @returns true if the pubkeys are equal + */ + equals(other: Pubkey): boolean { + return this._wasm.equals(other._wasm); + } + + /** + * Check if this public key is on the Ed25519 curve. + * + * Regular Solana keypair addresses are on the curve, while Program Derived Addresses (PDAs) + * are intentionally off the curve to ensure they can only be signed by programs. + * + * This is equivalent to `@solana/web3.js` `PublicKey.isOnCurve()`. + * + * @returns true if the public key is on the Ed25519 curve + */ + isOnCurve(): boolean { + return this._wasm.is_on_curve(); + } + + /** + * Get the underlying WASM instance (internal use only) + * @internal + */ + get wasm(): WasmPubkey { + return this._wasm; + } +} diff --git a/packages/wasm-solana/package.json b/packages/wasm-solana/package.json new file mode 100644 index 0000000..562aca2 --- /dev/null +++ b/packages/wasm-solana/package.json @@ -0,0 +1,63 @@ +{ + "name": "@bitgo/wasm-solana", + "description": "WebAssembly wrapper for Solana cryptographic operations", + "version": "0.0.1", + "private": true, + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/BitGo/BitGoWASM" + }, + "license": "MIT", + "files": [ + "dist/esm/js/**/*", + "dist/cjs/js/**/*", + "dist/cjs/package.json" + ], + "exports": { + ".": { + "import": { + "types": "./dist/esm/js/index.d.ts", + "default": "./dist/esm/js/index.js" + }, + "require": { + "types": "./dist/cjs/js/index.d.ts", + "default": "./dist/cjs/js/index.js" + } + } + }, + "main": "./dist/cjs/js/index.js", + "module": "./dist/esm/js/index.js", + "types": "./dist/esm/js/index.d.ts", + "sideEffects": [ + "./dist/esm/js/wasm/wasm_solana.js", + "./dist/cjs/js/wasm/wasm_solana.js" + ], + "scripts": { + "test": "npm run test:mocha", + "test:mocha": "mocha --recursive test", + "build:wasm": "make js/wasm && make dist/esm/js/wasm && make dist/cjs/js/wasm", + "build:ts-esm": "tsc", + "build:ts-cjs": "tsc --project tsconfig.cjs.json", + "build:ts": "npm run build:ts-esm && npm run build:ts-cjs", + "build:package-json": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json", + "build": "npm run build:wasm && npm run build:ts && npm run build:package-json", + "check-fmt": "prettier --check . && cargo fmt -- --check", + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/mocha": "^10.0.7", + "@types/node": "^22.10.5", + "eslint": "^9.17.0", + "mocha": "^10.6.0", + "tsx": "4.20.6", + "typescript": "^5.5.3", + "typescript-eslint": "^8.18.2" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/wasm-solana/src/error.rs b/packages/wasm-solana/src/error.rs new file mode 100644 index 0000000..18bde38 --- /dev/null +++ b/packages/wasm-solana/src/error.rs @@ -0,0 +1,43 @@ +use core::fmt; +use wasm_bindgen::prelude::*; + +#[derive(Debug, Clone)] +pub enum WasmSolanaError { + StringError(String), +} + +impl std::error::Error for WasmSolanaError {} + +impl fmt::Display for WasmSolanaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + WasmSolanaError::StringError(s) => write!(f, "{}", s), + } + } +} + +impl From<&str> for WasmSolanaError { + fn from(s: &str) -> Self { + WasmSolanaError::StringError(s.to_string()) + } +} + +impl From for WasmSolanaError { + fn from(s: String) -> Self { + WasmSolanaError::StringError(s) + } +} + +impl WasmSolanaError { + pub fn new(s: &str) -> WasmSolanaError { + WasmSolanaError::StringError(s.to_string()) + } +} + +// Required for wasm_bindgen to convert errors to JavaScript exceptions +// Uses js_sys::Error to create a proper JavaScript Error with stack trace +impl From for JsValue { + fn from(err: WasmSolanaError) -> Self { + js_sys::Error::new(&err.to_string()).into() + } +} diff --git a/packages/wasm-solana/src/keypair.rs b/packages/wasm-solana/src/keypair.rs new file mode 100644 index 0000000..a8c5f80 --- /dev/null +++ b/packages/wasm-solana/src/keypair.rs @@ -0,0 +1,140 @@ +//! Ed25519 keypair implementation for Solana. +//! +//! Wraps `solana_keypair::Keypair` for WASM compatibility. + +use crate::error::WasmSolanaError; +use solana_signer::Signer; + +/// Re-export the underlying Solana Keypair type. +pub use solana_keypair::Keypair; + +/// Extension trait for Keypair to add WASM-friendly methods. +pub trait KeypairExt { + fn from_secret_key_bytes(secret_key: &[u8]) -> Result; + fn from_solana_secret_key(secret_key: &[u8]) -> Result; + fn public_key_bytes(&self) -> [u8; 32]; + fn secret_key_bytes(&self) -> [u8; 32]; + fn address(&self) -> String; +} + +impl KeypairExt for Keypair { + /// Create a keypair from a 32-byte secret key (Ed25519 seed). + fn from_secret_key_bytes(secret_key: &[u8]) -> Result { + if secret_key.len() != 32 { + return Err(WasmSolanaError::new(&format!( + "Secret key must be 32 bytes, got {}", + secret_key.len() + ))); + } + + // Generate public key from secret to create full 64-byte format + use ed25519_dalek::SigningKey; + let bytes: [u8; 32] = secret_key + .try_into() + .map_err(|_| WasmSolanaError::new("Failed to convert secret key to array"))?; + let signing_key = SigningKey::from_bytes(&bytes); + let pubkey_bytes = signing_key.verifying_key().to_bytes(); + + let mut full_secret = [0u8; 64]; + full_secret[..32].copy_from_slice(secret_key); + full_secret[32..].copy_from_slice(&pubkey_bytes); + + Keypair::try_from(full_secret.as_slice()) + .map_err(|e| WasmSolanaError::new(&format!("Invalid keypair: {}", e))) + } + + /// Create a keypair from a 64-byte Solana secret key (secret + public concatenated). + fn from_solana_secret_key(secret_key: &[u8]) -> Result { + if secret_key.len() != 64 { + return Err(WasmSolanaError::new(&format!( + "Solana secret key must be 64 bytes, got {}", + secret_key.len() + ))); + } + + Keypair::try_from(secret_key) + .map_err(|e| WasmSolanaError::new(&format!("Invalid keypair: {}", e))) + } + + /// Get the public key bytes (32 bytes). + fn public_key_bytes(&self) -> [u8; 32] { + self.pubkey().to_bytes() + } + + /// Get the secret key bytes (32 bytes, the seed only). + fn secret_key_bytes(&self) -> [u8; 32] { + let bytes = self.to_bytes(); + let mut secret = [0u8; 32]; + secret.copy_from_slice(&bytes[..32]); + secret + } + + /// Get the Solana address (base58-encoded public key). + fn address(&self) -> String { + self.pubkey().to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_keypair() { + let keypair = Keypair::new(); + assert_eq!(keypair.public_key_bytes().len(), 32); + assert_eq!(keypair.secret_key_bytes().len(), 32); + } + + #[test] + fn test_from_secret_key() { + let secret = [1u8; 32]; + let keypair = Keypair::from_secret_key_bytes(&secret).unwrap(); + assert_eq!(keypair.secret_key_bytes(), secret); + } + + #[test] + fn test_deterministic_pubkey() { + let secret = [1u8; 32]; + let keypair1 = Keypair::from_secret_key_bytes(&secret).unwrap(); + let keypair2 = Keypair::from_secret_key_bytes(&secret).unwrap(); + assert_eq!(keypair1.public_key_bytes(), keypair2.public_key_bytes()); + assert_eq!(keypair1.address(), keypair2.address()); + } + + #[test] + fn test_solana_secret_key_format() { + let secret = [1u8; 32]; + let keypair = Keypair::from_secret_key_bytes(&secret).unwrap(); + let pubkey = keypair.public_key_bytes(); + + // Create 64-byte Solana format + let mut solana_secret = [0u8; 64]; + solana_secret[..32].copy_from_slice(&secret); + solana_secret[32..].copy_from_slice(&pubkey); + + let keypair2 = Keypair::from_solana_secret_key(&solana_secret).unwrap(); + assert_eq!(keypair.address(), keypair2.address()); + } + + #[test] + fn test_invalid_secret_key_length() { + assert!(Keypair::from_secret_key_bytes(&[0u8; 31]).is_err()); + assert!(Keypair::from_secret_key_bytes(&[0u8; 33]).is_err()); + assert!(Keypair::from_solana_secret_key(&[0u8; 63]).is_err()); + assert!(Keypair::from_solana_secret_key(&[0u8; 65]).is_err()); + } + + /// Test vector from BitGoJS sdk-coin-sol + #[test] + fn test_bitgojs_compatibility() { + let seed: [u8; 32] = [ + 210, 49, 239, 175, 249, 91, 42, 66, 77, 70, 3, 144, 23, 0, 145, 152, 86, 35, 166, 11, + 129, 49, 201, 162, 255, 195, 94, 229, 98, 78, 76, 38, + ]; + let expected_address = "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH"; + + let keypair = Keypair::from_secret_key_bytes(&seed).unwrap(); + assert_eq!(keypair.address(), expected_address); + } +} diff --git a/packages/wasm-solana/src/lib.rs b/packages/wasm-solana/src/lib.rs new file mode 100644 index 0000000..fa67d30 --- /dev/null +++ b/packages/wasm-solana/src/lib.rs @@ -0,0 +1,37 @@ +//! wasm-solana: WASM bindings for Solana cryptographic operations. +//! +//! This crate wraps the official Solana SDK crates (`solana-pubkey`, `solana-keypair`) +//! and exposes them via WASM bindings for use in JavaScript/TypeScript. +//! +//! # Architecture +//! +//! The crate follows a two-layer architecture: +//! +//! 1. **Core types** (`keypair`, `pubkey`) - Re-exports from Solana SDK with extension traits +//! 2. **WASM bindings** (`wasm/`) - Thin wrappers that expose core types to JavaScript +//! +//! # Usage from Rust +//! +//! ```rust +//! use wasm_solana::{Keypair, Pubkey, KeypairExt, PubkeyExt}; +//! +//! // Generate a new keypair +//! let keypair = Keypair::new(); +//! let address = keypair.address(); +//! +//! // Parse an address +//! let pubkey = Pubkey::from_base58("FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH").unwrap(); +//! ``` + +mod error; +pub mod keypair; +pub mod pubkey; +pub mod wasm; + +// Re-export core types at crate root +pub use error::WasmSolanaError; +pub use keypair::{Keypair, KeypairExt}; +pub use pubkey::{Pubkey, PubkeyExt}; + +// Re-export WASM types +pub use wasm::{WasmKeypair, WasmPubkey}; diff --git a/packages/wasm-solana/src/pubkey.rs b/packages/wasm-solana/src/pubkey.rs new file mode 100644 index 0000000..01c50df --- /dev/null +++ b/packages/wasm-solana/src/pubkey.rs @@ -0,0 +1,110 @@ +//! Solana public key (address) implementation. +//! +//! Wraps `solana_pubkey::Pubkey` for WASM compatibility. + +use crate::error::WasmSolanaError; +use std::str::FromStr; + +/// Re-export the underlying Solana Pubkey type. +pub use solana_pubkey::Pubkey; + +/// Extension trait for Pubkey to add WASM-friendly error handling. +pub trait PubkeyExt { + fn from_base58(address: &str) -> Result; + fn from_bytes_checked(bytes: &[u8]) -> Result; +} + +impl PubkeyExt for Pubkey { + /// Create a Pubkey from a base58 string with WasmSolanaError. + fn from_base58(address: &str) -> Result { + Pubkey::from_str(address) + .map_err(|e| WasmSolanaError::new(&format!("Invalid base58: {}", e))) + } + + /// Create a Pubkey from a byte slice with length validation. + fn from_bytes_checked(bytes: &[u8]) -> Result { + if bytes.len() != 32 { + return Err(WasmSolanaError::new(&format!( + "Invalid public key length: expected 32 bytes, got {}", + bytes.len() + ))); + } + + let array: [u8; 32] = bytes + .try_into() + .map_err(|_| WasmSolanaError::new("Failed to convert to 32-byte array"))?; + + Ok(Pubkey::from(array)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_base58() { + let address = "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH"; + let pubkey = Pubkey::from_base58(address).unwrap(); + assert_eq!(pubkey.to_string(), address); + } + + #[test] + fn test_from_bytes() { + let bytes = [0u8; 32]; + let pubkey = Pubkey::from_bytes_checked(&bytes).unwrap(); + assert_eq!(pubkey.to_bytes(), bytes); + } + + #[test] + fn test_roundtrip() { + let address = "11111111111111111111111111111111"; + let pubkey = Pubkey::from_base58(address).unwrap(); + assert_eq!(pubkey.to_string(), address); + } + + #[test] + fn test_invalid_base58() { + assert!(Pubkey::from_base58("invalid!@#$").is_err()); + } + + #[test] + fn test_invalid_length() { + assert!(Pubkey::from_bytes_checked(&[0u8; 31]).is_err()); + assert!(Pubkey::from_bytes_checked(&[0u8; 33]).is_err()); + } + + #[test] + fn test_display() { + let address = "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH"; + let pubkey = Pubkey::from_base58(address).unwrap(); + assert_eq!(format!("{}", pubkey), address); + } + + #[test] + fn test_equality() { + let addr1 = Pubkey::from_base58("11111111111111111111111111111111").unwrap(); + let addr2 = Pubkey::from_bytes_checked(&[0u8; 32]).unwrap(); + assert_eq!(addr1, addr2); + } + + #[test] + fn test_is_on_curve_valid_keypair() { + let pubkey = Pubkey::from_base58("FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH").unwrap(); + assert!(pubkey.is_on_curve()); + } + + #[test] + fn test_is_on_curve_off_curve_bytes() { + // Find bytes that are NOT on the Ed25519 curve + for seed in 1u8..=255 { + let mut bytes = [seed; 32]; + bytes[31] = 0x80 | seed; + let pubkey = Pubkey::from_bytes_checked(&bytes).unwrap(); + if !pubkey.is_on_curve() { + return; + } + } + panic!("Could not find an off-curve point"); + } +} diff --git a/packages/wasm-solana/src/wasm/keypair.rs b/packages/wasm-solana/src/wasm/keypair.rs new file mode 100644 index 0000000..aeb411f --- /dev/null +++ b/packages/wasm-solana/src/wasm/keypair.rs @@ -0,0 +1,62 @@ +//! WASM bindings for Solana keypair operations. +//! +//! Wraps `solana_keypair::Keypair` for JavaScript. + +use crate::error::WasmSolanaError; +use crate::keypair::{Keypair, KeypairExt}; +use wasm_bindgen::prelude::*; + +/// WASM wrapper for Solana Ed25519 keypairs. +#[wasm_bindgen] +#[derive(Debug)] +pub struct WasmKeypair { + inner: Keypair, +} + +#[wasm_bindgen] +impl WasmKeypair { + /// Create a keypair from a 32-byte secret key. + #[wasm_bindgen] + pub fn from_secret_key(secret_key: &[u8]) -> Result { + Keypair::from_secret_key_bytes(secret_key).map(|inner| WasmKeypair { inner }) + } + + /// Create a keypair from a 64-byte Solana secret key (secret + public concatenated). + #[wasm_bindgen] + pub fn from_solana_secret_key(secret_key: &[u8]) -> Result { + Keypair::from_solana_secret_key(secret_key).map(|inner| WasmKeypair { inner }) + } + + /// Get the public key as a 32-byte Uint8Array. + #[wasm_bindgen(getter)] + pub fn public_key(&self) -> js_sys::Uint8Array { + let bytes = self.inner.public_key_bytes(); + js_sys::Uint8Array::from(&bytes[..]) + } + + /// Get the secret key as a 32-byte Uint8Array. + #[wasm_bindgen(getter)] + pub fn secret_key(&self) -> js_sys::Uint8Array { + let bytes = self.inner.secret_key_bytes(); + js_sys::Uint8Array::from(&bytes[..]) + } + + /// Get the address as a base58 string. + #[wasm_bindgen] + pub fn address(&self) -> String { + self.inner.address() + } + + /// Get the public key as a base58 string. + #[wasm_bindgen] + pub fn to_base58(&self) -> String { + self.inner.address() + } +} + +impl WasmKeypair { + /// Get the inner Keypair for internal Rust use. + pub fn inner(&self) -> &Keypair { + &self.inner + } +} diff --git a/packages/wasm-solana/src/wasm/mod.rs b/packages/wasm-solana/src/wasm/mod.rs new file mode 100644 index 0000000..45092cd --- /dev/null +++ b/packages/wasm-solana/src/wasm/mod.rs @@ -0,0 +1,5 @@ +mod keypair; +mod pubkey; + +pub use keypair::WasmKeypair; +pub use pubkey::WasmPubkey; diff --git a/packages/wasm-solana/src/wasm/pubkey.rs b/packages/wasm-solana/src/wasm/pubkey.rs new file mode 100644 index 0000000..b92dedf --- /dev/null +++ b/packages/wasm-solana/src/wasm/pubkey.rs @@ -0,0 +1,66 @@ +//! WASM bindings for Solana public key (address) operations. +//! +//! Wraps `solana_pubkey::Pubkey` for JavaScript. + +use crate::error::WasmSolanaError; +use crate::pubkey::{Pubkey, PubkeyExt}; +use wasm_bindgen::prelude::*; + +/// WASM wrapper for Solana public key (address). +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub struct WasmPubkey { + inner: Pubkey, +} + +#[wasm_bindgen] +impl WasmPubkey { + /// Create a Pubkey from a base58 string. + #[wasm_bindgen] + pub fn from_base58(address: &str) -> Result { + Pubkey::from_base58(address).map(|inner| WasmPubkey { inner }) + } + + /// Create a Pubkey from raw bytes (32 bytes). + #[wasm_bindgen] + pub fn from_bytes(bytes: &[u8]) -> Result { + Pubkey::from_bytes_checked(bytes).map(|inner| WasmPubkey { inner }) + } + + /// Convert to base58 string (the standard Solana address format). + #[wasm_bindgen] + pub fn to_base58(&self) -> String { + self.inner.to_string() + } + + /// Get as raw bytes (32 bytes). + #[wasm_bindgen] + pub fn to_bytes(&self) -> js_sys::Uint8Array { + let bytes = self.inner.to_bytes(); + js_sys::Uint8Array::from(&bytes[..]) + } + + /// Check if two pubkeys are equal. + #[wasm_bindgen] + pub fn equals(&self, other: &WasmPubkey) -> bool { + self.inner == other.inner + } + + /// Check if this public key is on the Ed25519 curve. + #[wasm_bindgen] + pub fn is_on_curve(&self) -> bool { + self.inner.is_on_curve() + } +} + +impl WasmPubkey { + /// Create from inner Pubkey. + pub fn from_inner(inner: Pubkey) -> Self { + WasmPubkey { inner } + } + + /// Get the inner Pubkey for internal Rust use. + pub fn inner(&self) -> &Pubkey { + &self.inner + } +} diff --git a/packages/wasm-solana/test/compatibility.ts b/packages/wasm-solana/test/compatibility.ts new file mode 100644 index 0000000..512d64b --- /dev/null +++ b/packages/wasm-solana/test/compatibility.ts @@ -0,0 +1,80 @@ +import * as assert from "assert"; +import { Keypair, Pubkey } from "../js/index.js"; + +/** + * Compatibility tests to verify our WASM implementation produces the same + * results as BitGoJS sdk-coin-sol KeyPair. + * + * Test vectors from: BitGoJS/modules/sdk-coin-sol/test/resources/sol.ts + */ +describe("Compatibility with BitGoJS sdk-coin-sol", () => { + describe("accountWithSeed test vector", () => { + // From BitGoJS/modules/sdk-coin-sol/test/resources/sol.ts + const testVector = { + // 32-byte seed (Ed25519 secret key) + seed: new Uint8Array([ + 210, 49, 239, 175, 249, 91, 42, 66, 77, 70, 3, 144, 23, 0, 145, 152, 86, 35, 166, 11, 129, + 49, 201, 162, 255, 195, 94, 229, 98, 78, 76, 38, + ]), + // Expected public key / address + publicKey: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH", + // 64-byte Solana secret key format (seed + public key) + solanaSecretKey: new Uint8Array([ + 210, 49, 239, 175, 249, 91, 42, 66, 77, 70, 3, 144, 23, 0, 145, 152, 86, 35, 166, 11, 129, + 49, 201, 162, 255, 195, 94, 229, 98, 78, 76, 38, 212, 208, 16, 9, 69, 152, 60, 244, 226, 41, + 142, 209, 252, 78, 138, 101, 66, 156, 232, 39, 235, 224, 69, 45, 62, 111, 249, 253, 44, 80, + 162, 48, + ]), + }; + + it("should derive same address from seed as BitGoJS KeyPair", () => { + // BitGoJS: new KeyPair({ seed: testData.accountWithSeed.seed }).getAddress() + const keypair = Keypair.fromSecretKey(testVector.seed); + const address = keypair.getAddress(); + + assert.strictEqual( + address, + testVector.publicKey, + `Address mismatch!\n Expected: ${testVector.publicKey}\n Got: ${address}`, + ); + }); + + it("should derive same address from 64-byte Solana secret key", () => { + // BitGoJS: new KeyPair({ prv: base58(solanaSecretKey) }).getAddress() + const keypair = Keypair.fromSolanaSecretKey(testVector.solanaSecretKey); + const address = keypair.getAddress(); + + assert.strictEqual(address, testVector.publicKey); + }); + + it("should parse public key and roundtrip correctly", () => { + // BitGoJS: new KeyPair({ pub: testData.accountWithSeed.publicKey }) + const pubkey = Pubkey.fromBase58(testVector.publicKey); + const roundtripped = pubkey.toBase58(); + + assert.strictEqual(roundtripped, testVector.publicKey); + }); + + it("should produce matching public key bytes", () => { + const keypair = Keypair.fromSecretKey(testVector.seed); + const pubkeyFromAddress = Pubkey.fromBase58(testVector.publicKey); + + // Public key bytes from keypair should match parsed address + assert.deepStrictEqual(keypair.publicKey, pubkeyFromAddress.toBytes()); + }); + }); + + describe("authAccount test vector", () => { + // Another test vector from BitGoJS + const authAccount = { + pub: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + }; + + it("should parse authAccount public key", () => { + const pubkey = Pubkey.fromBase58(authAccount.pub); + + assert.strictEqual(pubkey.toBase58(), authAccount.pub); + assert.strictEqual(pubkey.toBytes().length, 32); + }); + }); +}); diff --git a/packages/wasm-solana/test/keypair.ts b/packages/wasm-solana/test/keypair.ts new file mode 100644 index 0000000..b78c7c6 --- /dev/null +++ b/packages/wasm-solana/test/keypair.ts @@ -0,0 +1,36 @@ +import * as assert from "assert"; +import { Keypair } from "../js/keypair.js"; + +describe("Keypair", () => { + const testSecretKey = new Uint8Array(32).fill(1); + + it("should create keypair from secret key", () => { + const keypair = Keypair.fromSecretKey(testSecretKey); + + assert.strictEqual(keypair.publicKey.length, 32); + assert.deepStrictEqual(keypair.secretKey, testSecretKey); + }); + + it("should create keypair from 64-byte Solana secret key", () => { + const keypair1 = Keypair.fromSecretKey(testSecretKey); + + // Create 64-byte Solana format (secret + public) + const solanaSecretKey = new Uint8Array(64); + solanaSecretKey.set(testSecretKey, 0); + solanaSecretKey.set(keypair1.publicKey, 32); + + const keypair2 = Keypair.fromSolanaSecretKey(solanaSecretKey); + + assert.strictEqual(keypair2.getAddress(), keypair1.getAddress()); + }); + + it("should reject invalid secret key lengths", () => { + assert.throws(() => Keypair.fromSecretKey(new Uint8Array(31)), /32 bytes/); + assert.throws(() => Keypair.fromSecretKey(new Uint8Array(33)), /32 bytes/); + }); + + it("should reject invalid Solana secret key lengths", () => { + assert.throws(() => Keypair.fromSolanaSecretKey(new Uint8Array(63)), /64 bytes/); + assert.throws(() => Keypair.fromSolanaSecretKey(new Uint8Array(65)), /64 bytes/); + }); +}); diff --git a/packages/wasm-solana/test/pubkey.ts b/packages/wasm-solana/test/pubkey.ts new file mode 100644 index 0000000..740b8b9 --- /dev/null +++ b/packages/wasm-solana/test/pubkey.ts @@ -0,0 +1,44 @@ +import * as assert from "assert"; +import { Pubkey } from "../js/pubkey.js"; +import { Keypair } from "../js/keypair.js"; + +describe("Pubkey", () => { + const testAddress = "11111111111111111111111111111111"; + const testBytes = new Uint8Array(32).fill(0); + const testSecretKey = new Uint8Array(32).fill(1); + + it("should create Pubkey from base58 and bytes", () => { + const pubkey1 = Pubkey.fromBase58(testAddress); + const pubkey2 = Pubkey.fromBytes(testBytes); + + assert.strictEqual(pubkey1.toBase58(), testAddress); + assert.strictEqual(pubkey2.toBase58(), testAddress); + }); + + it("should roundtrip base58 -> bytes -> base58", () => { + const keypair = Keypair.fromSecretKey(testSecretKey); + const pubkey = Pubkey.fromBase58(keypair.getAddress()); + + assert.strictEqual(pubkey.toBase58(), keypair.getAddress()); + assert.deepStrictEqual(pubkey.toBytes(), keypair.publicKey); + }); + + it("should compare pubkeys for equality", () => { + const pubkey1 = Pubkey.fromBase58(testAddress); + const pubkey2 = Pubkey.fromBase58(testAddress); + + assert.ok(pubkey1.equals(pubkey2)); + }); + + it("should reject invalid inputs", () => { + assert.throws(() => Pubkey.fromBase58("invalid!@#$"), /Invalid base58/); + assert.throws(() => Pubkey.fromBytes(new Uint8Array(31)), /expected 32 bytes/); + }); + + it("isOnCurve should return true for keypair addresses", () => { + const keypair = Keypair.fromSecretKey(testSecretKey); + const pubkey = Pubkey.fromBytes(keypair.publicKey); + + assert.strictEqual(pubkey.isOnCurve(), true); + }); +}); diff --git a/packages/wasm-solana/tsconfig.cjs.json b/packages/wasm-solana/tsconfig.cjs.json new file mode 100644 index 0000000..f6f2384 --- /dev/null +++ b/packages/wasm-solana/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "node", + "rootDir": ".", + "outDir": "./dist/cjs" + }, + "exclude": ["test/**/*"] +} diff --git a/packages/wasm-solana/tsconfig.json b/packages/wasm-solana/tsconfig.json new file mode 100644 index 0000000..97f8a7a --- /dev/null +++ b/packages/wasm-solana/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "ES2022", + "target": "ES2022", + "moduleResolution": "bundler", + "esModuleInterop": true, + "allowJs": true, + "skipLibCheck": true, + "declaration": true, + "composite": true, + "rootDir": ".", + "outDir": "./dist/esm", + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["./js/**/*.ts", "test/**/*.ts"], + "exclude": ["node_modules", "./js/wasm/**/*"] +}