Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dist/
data/
bin/
release/
vendor/kascov-preflight/target/
config/compiler.json
config/kascov-preflight.local.json
*.log
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 0.2.7 — 2026-08-09

- Raised Studio's conservative covenant-cell default and minimum to 0.5 KAS/TKAS, preventing storage-mass rejection when funding a small covenant from a large faucet or mining UTXO.
- Made genesis deployment choose the lowest suitable UTXO, calculate the exact candidate mass, and return structured diagnostics before signing when no candidate is standard.
- Fixed Toccata v1 covenant and P2PK co-spend inputs to commit only `computeBudget`; non-zero legacy `sigOpCount` fields are now excluded because current nodes reject that mixed encoding.
- Moved lifecycle fee calculation after full redeem-program and argument assembly, consumes the pinned script engine's fee/mass report, and reserves the final CheckSig execution cost before collecting signatures.
- Kept phrase-free TN10 inheritance renewal fail-closed: only the exact current same-covenant continuation is accepted, with a dynamic fee capped at 0.02 TKAS.
- Added regression coverage for large-faucet-UTXO deployment, engine-driven fee adjustment, signed execution reserve, Toccata v1 input encoding, and the renewal fee safety cap.
- Completed live TN10 end-to-end verification for wallet transfer, covenant genesis, mature multi-inheritor distribution, and owner-signed check-in continuation; all final transactions passed the bundled engine and Kascov before node acceptance.
- Vendored the audited MIT-licensed Kascov preflight source snapshot and its lockfile after the upstream repository removed the pinned commit; clean macOS, Windows, and Linux builds no longer depend on that upstream source remaining online. The lockfile also applies minimal fixes for `RUSTSEC-2026-0204` and `RUSTSEC-2026-0220`.

## 0.2.7 — 2026-08-09(中文)

- Studio 的保守 Covenant Cell 默认值与最低值统一提高到 0.5 KAS/TKAS,避免从水龙头或挖矿大额 UTXO 拆出过小契约输出时被存储质量规则拒绝。
- 部署构建器会优先选择金额最小的合适 UTXO、逐笔计算真实质量;若没有标准交易候选,会在签名前返回结构化诊断。
- 修复 Toccata v1 契约输入与 P2PK 共同授权输入:只提交 `computeBudget`,不再混入当前节点会拒绝的旧版非零 `sigOpCount`。
- 生命周期手续费改为在 redeem program 与全部参数完整装配后计算,读取固定脚本引擎的费用/质量报告,并在收集签名前预留真实 CheckSig 执行费用。
- TN10 免短语继承签到继续保持失效关闭:只允许当前项目、当前 UTXO、同 covenant ID 的唯一延续输出,动态手续费上限为 0.02 TKAS。
- 增加大额水龙头 UTXO 部署、引擎动态费率、签名执行预留、Toccata v1 字段编码及续期费用上限的回归测试。
- 已在真实 TN10 完成钱包转账、契约创世、多继承人成熟分配、拥有者签名签到延续的端到端验证;最终交易均先通过内置引擎和 Kascov,再由节点接受。
- 在上游仓库删除原固定提交后,将已审计的 MIT 许可 Kascov 预检源码快照与锁文件直接纳入仓库;macOS、Windows、Linux 的干净构建不再依赖该上游源码持续在线,并以最小升级修复 `RUSTSEC-2026-0204` 与 `RUSTSEC-2026-0220`。

## 0.2.6 — 2026-08-09

- Updated the default SHA-256-pinned official SilverScript compiler to `cb34aa5e6a598f9e461c4ad7014279ba89251d8d`; the `2a3961c` legacy profile remains available for reproducibility.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ npm ci

首次运行需要在当前系统构建固定版本的两个原生工具。以下命令由 Node.js 驱动,可直接用于 macOS、Windows 和 Linux:

Kascov 预检引擎使用仓库内置的 MIT 许可源码快照和 Cargo 锁文件构建,不依赖 Kascov 网站或其历史 Git 提交继续在线。

```bash
npm run setup:silverc
npm run setup:kascov-preflight
Expand Down Expand Up @@ -338,6 +340,8 @@ npm ci

Build the two pinned native tools for the current platform. These Node.js-driven commands run directly on macOS, Windows, and Linux:

The Kascov preflight helper builds from the committed MIT-licensed source snapshot and Cargo lockfile, so it does not depend on the Kascov website or a historical upstream Git commit remaining online.

```bash
npm run setup:silverc
npm run setup:kascov-preflight
Expand Down
4 changes: 3 additions & 1 deletion config/kascov-preflight.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"repository": "https://github.com/Knitser/kascov.git",
"upstreamCommit": "b64d6b4114df324f899783080371f26b619b19d0",
"rustyKaspaCommit": "98a4ccd8d200853787f227bd4536ac540cf34957",
"sourceModule": "crates/kascov/src/preflight.rs",
"sourceMode": "vendored-mit-snapshot",
"sourceModule": "vendor/kascov-preflight/crates/studio-kascov-preflight/src/preflight.rs",
"license": "vendor/kascov-preflight/LICENSE",
"binary": "bin/kascov-preflight",
"purpose": "Offline transaction preflight using the same Kaspa script engine path as Kascov"
}
45 changes: 45 additions & 0 deletions docs/releases/v0.2.7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## Kaspa SilverScript Studio v0.2.7

This release fixes three transaction-construction defects found by exercising the packaged Studio against the live Kaspa TN10 network, not only against mocks.

### Transaction correctness

- Studio now applies a conservative 0.5 KAS/TKAS covenant-cell floor. This keeps a genesis split funded from a large faucet or mining UTXO within both the bundled wallet calculator and current node storage-mass limits; it is not presented as a consensus dust rule.
- Genesis deployment evaluates eligible UTXOs from smallest to largest and records exact mass diagnostics before any wallet signature.
- Toccata v1 covenant and P2PK co-spend inputs now use `computeBudget` with a zero legacy `sigOpCount`, matching the current node RPC and consensus encoding.
- Lifecycle packages are fully assembled before fee calculation. Studio consumes the pinned local script-engine mass/fee report and adds a conservative reserve for each real CheckSig before asking anyone to sign.
- Phrase-free TN10 inheritance check-in remains narrowly authorized and fail-closed, with a 0.02 TKAS maximum automatic fee.
- The audited MIT-licensed Kascov preflight source snapshot and Cargo lockfile are now committed with Studio. Rebuilding the local engine no longer depends on the removed upstream commit remaining reachable; minimal transitive updates address `RUSTSEC-2026-0204` and `RUSTSEC-2026-0220`.

### Live TN10 evidence

- Wallet transfer: [`f3734e73…`](https://kascov.io/#/testnet-10/tx/f3734e73bd96c0a00437b15298dcc284004af57386786278905a00cd15de53d5)
- 0.5 TKAS inheritance covenant genesis: [`94d0414e…`](https://kascov.io/#/testnet-10/tx/94d0414ecb2b4c119841bbec5b732e882b82b3713abfca563b32975086aaa656)
- Mature 50/50 inheritance distribution: [`185bac65…`](https://kascov.io/#/testnet-10/tx/185bac653855e7b99f36c7ac0f4b97af4b911ed1a88531ec5a9fb1b6e5ca4fd2)
- Owner-signed same-covenant check-in continuation: [`103e9047…`](https://kascov.io/#/testnet-10/tx/103e9047cdffec4dd41f3b4632b09898fa78547a6d9a54ae0cc3093692adfb28)

The final inheritance distribution and renewal both passed the bundled pinned Kaspa script engine and Kascov preflight before node submission. Automated tests cover 43 Studio behaviors; npm reports zero known vulnerabilities. Mainnet remains disabled by default, and SilverScript remains experimental.

---

## 中文说明

本版修复了三个只有把打包后的 Studio 接入真实 Kaspa TN10 网络后才暴露的交易构建问题,而不是只依赖模拟测试。

### 交易正确性

- Studio 现在采用保守的 0.5 KAS/TKAS Covenant Cell 下限,避免从水龙头或挖矿大额 UTXO 拆分时超过内置钱包计算器与当前节点的存储质量限制;这不是共识层 dust 规则。
- 契约创世部署会从小到大评估可用 UTXO,并在请求钱包签名前记录精确质量诊断。
- Toccata v1 契约输入和 P2PK 共同授权输入现在只使用 `computeBudget`,旧版 `sigOpCount` 固定为零,与当前节点 RPC 和共识编码一致。
- 生命周期操作包会先完整装配 redeem program 和参数,再读取固定本地脚本引擎的质量/费用报告;每个真实 CheckSig 都会在签名前加入保守费用预留。
- TN10 免短语继承签到仍严格限制在当前项目、当前 UTXO 和同 Covenant 延续,自动手续费上限为 0.02 TKAS。
- 已审计的 MIT 许可 Kascov 预检源码快照和 Cargo 锁文件现已随 Studio 一同提交;重建本地引擎不再依赖已被上游删除的历史提交仍可访问,并以最小依赖升级修复 `RUSTSEC-2026-0204` 与 `RUSTSEC-2026-0220`。

### 真实 TN10 证据

- 钱包转账:[`f3734e73…`](https://kascov.io/#/testnet-10/tx/f3734e73bd96c0a00437b15298dcc284004af57386786278905a00cd15de53d5)
- 0.5 TKAS 继承契约创世:[`94d0414e…`](https://kascov.io/#/testnet-10/tx/94d0414ecb2b4c119841bbec5b732e882b82b3713abfca563b32975086aaa656)
- 成熟后的 50/50 继承分配:[`185bac65…`](https://kascov.io/#/testnet-10/tx/185bac653855e7b99f36c7ac0f4b97af4b911ed1a88531ec5a9fb1b6e5ca4fd2)
- 拥有者签名、同 Covenant 延续的签到交易:[`103e9047…`](https://kascov.io/#/testnet-10/tx/103e9047cdffec4dd41f3b4632b09898fa78547a6d9a54ae0cc3093692adfb28)

最终继承分配与签到续期在提交节点前都通过了内置固定 Kaspa 脚本引擎和 Kascov 预检。自动化测试覆盖 43 项 Studio 行为,npm 已知漏洞为零。主网继续默认关闭,SilverScript 仍处于实验阶段。
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ <h2 data-i18n="projects">契约项目</h2>
<div class="card-heading"><div><span class="eyebrow">DEPLOYMENT INTENT</span><h3 data-i18n="fundCovenant">为 Covenant 创建链上 UTXO</h3></div><span class="step-chip">04 / SIGN</span></div>
<div class="form-grid">
<label><span data-i18n="network">网络</span><select id="deploy-network"><option value="tn10">TN10 · Testnet 10</option><option value="mainnet">Mainnet</option></select></label>
<label><span data-i18n="amount">锁定金额</span><div class="amount-input"><input id="deploy-amount" value="0.05" inputmode="decimal"/><b id="deploy-symbol">TKAS</b></div></label>
<label><span data-i18n="amount">锁定金额</span><div class="amount-input"><input id="deploy-amount" value="0.5" min="0.5" inputmode="decimal"/><b id="deploy-symbol">TKAS</b></div></label>
</div>
<label class="confirmation" id="mainnet-confirmation" hidden><span data-i18n="mainnetPhrase">主网确认短语</span><input id="mainnet-phrase" placeholder="DEPLOY REAL KAS" autocomplete="off" /></label>
<div class="wallet-card" id="wallet-card"><span class="wallet-icon">◇</span><div><strong data-i18n="walletNotConnected">钱包未连接</strong><small data-i18n="walletHelp">使用内置加密钱包在本机完成签名</small></div></div>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kaspa-silverscript-studio",
"version": "0.2.6",
"version": "0.2.7",
"private": true,
"type": "module",
"description": "Local bilingual AI-assisted SilverScript contract studio for Kaspa",
Expand Down
86 changes: 10 additions & 76 deletions scripts/build-kascov-preflight.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,95 +6,29 @@ import { fileURLToPath } from "node:url";
import { cargoReleaseBinary, executableName, makeExecutable } from "./platform-binaries.mjs";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repositoryUrl = process.env.KASCOV_REPOSITORY || "https://github.com/Knitser/kascov.git";
const commit = process.env.KASCOV_COMMIT || "b64d6b4114df324f899783080371f26b619b19d0";
const work = path.resolve(process.env.KASCOV_BUILD_DIR || path.join(root, ".build", `kascov-${commit}`));
const workspace = path.join(root, "vendor", "kascov-preflight");
const manifest = path.join(workspace, "Cargo.toml");
const lock = path.join(workspace, "Cargo.lock");
const targetDirectory = path.resolve(process.env.CARGO_TARGET_DIR || path.join(root, ".build", "kascov-target"));
const output = path.join(root, "bin", executableName("kascov-preflight"));
const packageName = "studio-kascov-preflight";

function run(command, args, options = {}) {
execFileSync(command, args, { stdio: "inherit", ...options });
}

function packageIdentities(lock) {
return lock
.replace(/\r\n/g, "\n")
.split(/\n(?=\[\[package\]\]\n)/)
.filter((section) => section.startsWith("[[package]]\n"))
.map((section) => {
const field = (name) => section.match(new RegExp(`^${name} = "([^"]*)"$`, "m"))?.[1] || "";
return JSON.stringify([field("name"), field("version"), field("source"), field("checksum")]);
});
if (!fs.existsSync(manifest) || !fs.existsSync(lock)) {
throw new Error("Vendored Kascov preflight workspace is incomplete");
}

if (!fs.existsSync(path.join(work, ".git"))) {
fs.rmSync(work, { recursive: true, force: true });
fs.mkdirSync(path.dirname(work), { recursive: true });
run("git", ["clone", "--filter=blob:none", "--no-checkout", repositoryUrl, work]);
}

run("git", ["-C", work, "fetch", "--depth", "1", "origin", commit]);
run("git", ["-C", work, "checkout", "--detach", "--force", commit]);
const lockFile = path.join(work, "Cargo.lock");
const pinnedLock = fs.readFileSync(lockFile, "utf8");
const crateDirectory = path.join(work, "crates", packageName);
const sourceDirectory = path.join(crateDirectory, "src");
fs.mkdirSync(sourceDirectory, { recursive: true });
fs.copyFileSync(path.join(root, "native", "kascov-preflight-main.rs"), path.join(sourceDirectory, "main.rs"));
const upstreamPreflightFile = path.join(work, "crates", "kascov", "src", "preflight.rs");
const upstreamPreflight = fs.readFileSync(upstreamPreflightFile, "utf8");
if (!upstreamPreflight.includes("use kascov_core::Network;")) throw new Error("Pinned Kascov preflight source has an unexpected Network import");
fs.writeFileSync(path.join(sourceDirectory, "preflight.rs"), upstreamPreflight.replace("use kascov_core::Network;", "use crate::Network;"));
fs.writeFileSync(path.join(crateDirectory, "Cargo.toml"), `[package]
name = "${packageName}"
version.workspace = true
edition.workspace = true
license.workspace = true

[dependencies]
kascov-decode = { workspace = true }
kascov-sim = { workspace = true }
kaspa-consensus-core = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
hex = { workspace = true }

[[bin]]
name = "kascov-preflight"
path = "src/main.rs"
`);
const workspaceManifestFile = path.join(work, "Cargo.toml");
const workspaceManifest = fs.readFileSync(workspaceManifestFile, "utf8");
if (!workspaceManifest.includes("members = [")) throw new Error("Pinned Kascov workspace manifest has an unexpected members declaration");
fs.writeFileSync(workspaceManifestFile, workspaceManifest.replace("members = [", `members = ["crates/${packageName}", `));
const buildArgs = [
"build",
"--manifest-path", workspaceManifestFile,
"--release",
"-p", packageName,
"--bin", "kascov-preflight"
];
// Cargo must first register the injected local package in the upstream lockfile.
// The existing lockfile supplies every external resolution. Target-specific
// dependency sections can be rewritten on Windows, so compare immutable package
// identities instead of formatting: every pinned name/version/source/checksum
// tuple must remain present before repeating the build under --locked.
run("cargo", buildArgs, { env: { ...process.env, CARGO_TARGET_DIR: targetDirectory } });
const updatedLock = fs.readFileSync(lockFile, "utf8");
const pinnedPackages = packageIdentities(pinnedLock);
const updatedPackages = new Set(packageIdentities(updatedLock));
for (const identity of pinnedPackages) {
if (!updatedPackages.has(identity)) throw new Error(`Pinned Kascov dependency drifted while adding the local preflight package: ${identity}`);
}
if (![...updatedPackages].some((identity) => JSON.parse(identity)[0] === packageName)) {
throw new Error("Cargo did not register the local Kascov preflight package");
}
run("cargo", [
"build",
"--locked",
...buildArgs.slice(1)
"--manifest-path", manifest,
"--release",
"-p", "studio-kascov-preflight",
"--bin", "kascov-preflight"
], { env: { ...process.env, CARGO_TARGET_DIR: targetDirectory } });

fs.mkdirSync(path.dirname(output), { recursive: true });
fs.copyFileSync(cargoReleaseBinary(targetDirectory, "kascov-preflight"), output);
makeExecutable(output);
Expand Down
4 changes: 4 additions & 0 deletions scripts/check-syntax.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ for (const directory of ["server", "src", "scripts"]) {
}
for (const file of files) execFileSync(process.execPath, ["--check", file], { stdio: "inherit" });
const preflightManifest = JSON.parse(fs.readFileSync(path.join(root, "config", "kascov-preflight.json"), "utf8"));
if (preflightManifest.sourceMode !== "vendored-mit-snapshot") throw new Error("Local preflight must use the vendored source snapshot");
for (const requiredPath of [preflightManifest.sourceModule, preflightManifest.license, "vendor/kascov-preflight/Cargo.lock"]) {
if (!requiredPath || !fs.existsSync(path.join(root, requiredPath))) throw new Error(`Vendored preflight source is incomplete: ${requiredPath || "missing manifest path"}`);
}
const preflightLocalManifestFile = path.join(root, "config", "kascov-preflight.local.json");
if (!fs.existsSync(preflightLocalManifestFile)) throw new Error("Local preflight manifest is missing. Run npm run setup:kascov-preflight.");
const preflightLocalManifest = JSON.parse(fs.readFileSync(preflightLocalManifestFile, "utf8"));
Expand Down
7 changes: 5 additions & 2 deletions server/atomic-covenant-builder.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ export function buildAtomicCovenantPackage({ network: networkId = "tn10", covena
previousOutpoint: outpoint,
signatureScript: "",
sequence: BigInt(item.sequence || 0),
sigOpCount: Number(item.sigOpCount || 0),
// Toccata v1 inputs use computeBudget; a non-zero legacy sig-op field is
// rejected by current nodes before script execution.
sigOpCount: 0,
computeBudget: Number(item.computeBudget || 120),
utxo: transactionUtxo(utxo)
};
Expand Down Expand Up @@ -139,7 +141,8 @@ export function buildAtomicCovenantPackage({ network: networkId = "tn10", covena
gas: 0n,
payload: ""
});
const sigOps = Math.max(1, transactionInputs.reduce((sum, input) => sum + Number(input.sigOpCount || 0), 0));
const covenantSignatures = metadata.reduce((sum, item) => sum + (item.arguments || []).filter((argument) => argument?.kind === "signature").length, 0);
const sigOps = Math.max(1, covenantSignatures + (p2pkAuthorization?.input ? 1 : 0));
if (!kaspa.updateTransactionMass(network.kaspaNetworkId, transaction, sigOps, true)) throw builderError("Atomic covenant transaction exceeds the current mass limit", "ATOMIC_MASS_LIMIT");
return {
version: 1,
Expand Down
Loading