Adds the three remaining integration layers for spec PR 7 (agent-registry keyless): a tier-1 suite that exercises the five build_* tools against the live testnet registry with zero secrets (build-only, nothing signed or submitted), a gated full lifecycle E2E (register -> update -> deregister) that actually submits, and a shared signWithMnemonic() helper extracted from keyless-e2e.test.ts so registry-e2e.test.ts and run.test.ts's registry section don't each re-inline the same CML signing sequence. run.test.ts's "Agent Network Tools" section is rewritten: each of the five old custodial calls is now build_* -> signWithMnemonic -> submit, via a new buildSignSubmit() helper that reuses the file's existing assertSuccessOrKnownError convention at both the build and submit legs. Found and fixed one load-bearing bug while proving this live, not just in review: the inline signing block moved verbatim per the brief's Step 1 built the signed tx's witness set from scratch, which is lossless only for a script-less payment (the one case it had been proven against). The registry family's build_* tools attach a minting policy and/or spending validator, so their unsigned CBOR already carries scripts/redeemers that the from-scratch witness set silently dropped - the ledger rejected the first live E2E attempt with MissingScriptWitnessesUTXOW / PPViewHashesDontMatch. Verified zero funds were lost (balance identical before/after the rejected submission - a mempool-level rejection never reaches a block) before fixing it by merging into the tx's existing witness set via CML's add_all_witnesses instead of discarding it. Re-run clean: all 3 E2E transactions confirmed on-chain, plus all 5 of the legacy suite's registry operations submitted successfully too (independent confirmation across both the mint and spend redeemer paths). Also widened one tier-1 regex that didn't tolerate the live tool's Markdown-bold label formatting. Live evidence (testnet, funded wallet, 8 real transactions total across the gated E2E and the legacy suite): balance 986.689510 -> 982.280960 AP3X, fully reconciled against transaction fees plus one message delivery to an owner-derived enterprise address. All tx hashes, explorer links, and the full reconciliation are in task-5-report.md. mnemonic.txt and the two temporary evidence-capture scripts were removed and confirmed absent before this commit. Gates: typecheck 0, unit 350/350, smoke 26/26, auth 12/12, boundary 4/4 - all re-verified after cleanup. E2E skip-path (VECTOR_E2E_SUBMIT unset) exits 0 with tests 0 for both keyless-e2e.test.ts and registry-e2e.test.ts, confirmed with mnemonic.txt genuinely absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
55 lines
3.0 KiB
TypeScript
55 lines
3.0 KiB
TypeScript
// packages/builder/test/integration/sign-helper.ts
|
|
//
|
|
// Shared local-signing helper for the integration suites. Stands in for the
|
|
// in-tree local signer (packages/signer) — same CML primitives (hash ->
|
|
// make_vkey_witness -> witness-set attach), without the signer's own
|
|
// decode/policy/audit path. Extracted verbatim (Task 5, spec PR 7) from the
|
|
// inline signing block keyless-e2e.test.ts proved against the live testnet in
|
|
// PR #9's E2E, so registry-e2e.test.ts and run.test.ts's registry section can
|
|
// share it instead of re-inlining the same sequence.
|
|
import { CML, walletFromSeed } from '@lucid-evolution/lucid';
|
|
|
|
/**
|
|
* Sign an unsigned transaction (CBOR hex) with the payment key derived from a
|
|
* mnemonic. Takes the mnemonic directly (not a pre-derived key) so every
|
|
* caller can go straight from `getMnemonic()` to a signed transaction in one
|
|
* call.
|
|
*
|
|
* CORRECTION vs. the original inline block this was extracted from: that
|
|
* block built the signed tx's witness set from scratch
|
|
* (`CML.TransactionWitnessSet.new()`, only ever given the new vkey witness),
|
|
* which is lossless only when the unsigned tx's own witness set is empty -
|
|
* true for a plain payment (keyless-e2e.test.ts's proven case: no script, no
|
|
* redeemer). The agent-registry build_* tools attach a minting policy and/or
|
|
* spending validator, so their unsigned CBOR already carries
|
|
* plutus_v3_scripts/redeemers in `tx.witness_set()`, matched to a
|
|
* script_data_hash baked into the tx BODY at build time. Replacing the
|
|
* witness set outright silently drops that content; the ledger then rejects
|
|
* the resigned tx with `MissingScriptWitnessesUTXOW` (script witness gone)
|
|
* and `PPViewHashesDontMatch` (the body's script_data_hash no longer matches
|
|
* a witness set the ledger now sees as script-free) - reproduced live via
|
|
* registry-e2e.test.ts's register step, see task-5-report.md. Fixed by
|
|
* merging the new vkey witness into the tx's EXISTING witness set with
|
|
* `add_all_witnesses` (CML's documented primitive for exactly this "add a
|
|
* signature to an already-witnessed tx" composition) instead of discarding
|
|
* it. Strictly more correct and a no-op behavior change for the script-less
|
|
* case, since there's nothing else to preserve there.
|
|
*/
|
|
export function signWithMnemonic(unsignedCborHex: string, mnemonic: string): { signedCborHex: string; txHash: string } {
|
|
const { paymentKey } = walletFromSeed(mnemonic, { network: 'Mainnet', accountIndex: 0 });
|
|
const tx = CML.Transaction.from_cbor_hex(unsignedCborHex);
|
|
const txHash = CML.hash_transaction(tx.body());
|
|
const sk = CML.PrivateKey.from_bech32(paymentKey);
|
|
const vkeyWitness = CML.make_vkey_witness(txHash, sk);
|
|
|
|
const witnessSet = tx.witness_set();
|
|
const newWitnesses = CML.TransactionWitnessSet.new();
|
|
const vkeys = CML.VkeywitnessList.new();
|
|
vkeys.add(vkeyWitness);
|
|
newWitnesses.set_vkeywitnesses(vkeys);
|
|
witnessSet.add_all_witnesses(newWitnesses);
|
|
|
|
const signed = CML.Transaction.new(tx.body(), witnessSet, tx.is_valid(), tx.auxiliary_data());
|
|
return { signedCborHex: signed.to_cbor_hex(), txHash: txHash.to_hex() };
|
|
}
|