diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..a013dd6
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,13 @@
+# Docker build context is the repository root (see RemoteSigner/Dockerfile).
+**/.git
+**/.github
+**/bin
+**/obj
+**/.DS_Store
+**/*.user
+RemoteSigner.Tests/
+RemoteSigner.SeedCeremony/
+vendor/nspark/tests/
+vendor/nspark/samples/
+vendor/nspark/benchmarks/
+vendor/nspark/docs/
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b77949e..b3f7534 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,6 +18,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ submodules: true
- name: Setup .NET
uses: actions/setup-dotnet@v4
@@ -43,6 +45,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ submodules: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -68,7 +72,8 @@ jobs:
- name: Build and push
uses: docker/build-push-action@v6
with:
- context: ./RemoteSigner
+ context: .
+ file: RemoteSigner/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..f969226
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+[submodule "vendor/nspark"]
+ path = vendor/nspark
+ url = https://github.com/Elenpay/nspark.git
+ branch = main
diff --git a/RemoteSigner.Tests/Fixtures/golden-psbt-signatures.json b/RemoteSigner.Tests/Fixtures/golden-psbt-signatures.json
new file mode 100644
index 0000000..2b17b16
--- /dev/null
+++ b/RemoteSigner.Tests/Fixtures/golden-psbt-signatures.json
@@ -0,0 +1,44 @@
+[
+ {
+ "name": "p2wsh-2of3-all",
+ "psbt": "cHNidP8BAF4BAAAAAcbYkt1iwOa6IsI8lrNx1DWQCCg/y7\u002BfQTlfEhDIKOWVAAAAAAD/////AeiN9QUAAAAAIgAgg\u002BaofANl6wKKByTgFl5yBnqUK8f7sn4ULhAAIJb1C0cAAAAATwEENYfPAy8RJCyAAAAB/DvuQjoBjOttImoGYyiO0Pte4PqdeQqzcNAw4Ecw5sgDgI4uHNSCvdBxlpQ8WoEz0WmvhgIra7A4F3FkTsB0RNcQH8zk3jAAAIABAACAAQAAgE8BBDWHzwNWrAP0gAAAAfkIrkpmsP\u002BhqxS1WvDOSPKnAiXLkBCQLWkBr5C5Po\u002BBAlGvFeBbuLfqwYlbP19H/\u002B/s2DIaAu8iKY\u002BJ0KIDffBgEGDzoLMwAACAAQAAgAEAAIBPAQQ1h88DfblGjYAAAAH1InDHaHo6\u002BzUe9PG5owwQ87bTkhcGg66pSIwTmhHJmAMiI4UjOOpn\u002B/2Nw1KrJiXnmid2RiEja/HAITCQ00ienxDtAhDIMAAAgAEAAIABAACAAAEBK2SQ9QUAAAAAIgAguNLINpkV//IIFd1ti2ig15\u002B6mPOhNWykV0mwsneO9FciAgMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BkcwRAIgPcwj6yaA6RZn\u002B4YSHi4S1WE5ziHEt0IZO5KqDE5B0zMCID6cSLumR2AbgwqMTI3/Z3szEyMQauxtzvBpY8Z4oSp8AgEDBAIAAAABBWlSIQMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BiEDgTQLkhqca3brBTunNmjIsb4WEsFryTwd3BH/ZPS4KkohA91uD9EYRlzIBT6yNU2S2L/wvOA0/em4ocaM//veOtN2U64iBgMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BhgfzOTeMAAAgAEAAIABAACAAQAAAAAAAAAiBgOBNAuSGpxrdusFO6c2aMixvhYSwWvJPB3cEf9k9LgqShjtAhDIMAAAgAEAAIABAACAAQAAAAAAAAAiBgPdbg/RGEZcyAU\u002BsjVNkti/8LzgNP3puKHGjP/73jrTdhhg86CzMAAAgAEAAIABAACAAQAAAAAAAAAAAA==",
+ "sigHash": "All",
+ "network": "Regtest",
+ "expected": "cHNidP8BAF4BAAAAAcbYkt1iwOa6IsI8lrNx1DWQCCg/y7\u002BfQTlfEhDIKOWVAAAAAAD/////AeiN9QUAAAAAIgAgg\u002BaofANl6wKKByTgFl5yBnqUK8f7sn4ULhAAIJb1C0cAAAAATwEENYfPAy8RJCyAAAAB/DvuQjoBjOttImoGYyiO0Pte4PqdeQqzcNAw4Ecw5sgDgI4uHNSCvdBxlpQ8WoEz0WmvhgIra7A4F3FkTsB0RNcQH8zk3jAAAIABAACAAQAAgE8BBDWHzwNWrAP0gAAAAfkIrkpmsP\u002BhqxS1WvDOSPKnAiXLkBCQLWkBr5C5Po\u002BBAlGvFeBbuLfqwYlbP19H/\u002B/s2DIaAu8iKY\u002BJ0KIDffBgEGDzoLMwAACAAQAAgAEAAIBPAQQ1h88DfblGjYAAAAH1InDHaHo6\u002BzUe9PG5owwQ87bTkhcGg66pSIwTmhHJmAMiI4UjOOpn\u002B/2Nw1KrJiXnmid2RiEja/HAITCQ00ienxDtAhDIMAAAgAEAAIABAACAAAEBK2SQ9QUAAAAAIgAguNLINpkV//IIFd1ti2ig15\u002B6mPOhNWykV0mwsneO9FciAgMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BkcwRAIgPcwj6yaA6RZn\u002B4YSHi4S1WE5ziHEt0IZO5KqDE5B0zMCID6cSLumR2AbgwqMTI3/Z3szEyMQauxtzvBpY8Z4oSp8AiICA4E0C5IanGt26wU7pzZoyLG\u002BFhLBa8k8HdwR/2T0uCpKRzBEAiBFGR\u002BvauPZsjnrTMioqL3pSpWJwNdLxl4O84IrB/NajgIge3JAi2Yynykj2USpjdTQu2/Da/Vv2FuvG20RKrd/\u002B3ABAQMEAQAAAAEFaVIhAydCo1oxPZjPjswT4KqGwQwr18HWxBEa9sFCQ9q7nbgGIQOBNAuSGpxrdusFO6c2aMixvhYSwWvJPB3cEf9k9LgqSiED3W4P0RhGXMgFPrI1TZLYv/C84DT96bihxoz/\u002B94603ZTriIGAydCo1oxPZjPjswT4KqGwQwr18HWxBEa9sFCQ9q7nbgGGB/M5N4wAACAAQAAgAEAAIABAAAAAAAAACIGA4E0C5IanGt26wU7pzZoyLG\u002BFhLBa8k8HdwR/2T0uCpKGO0CEMgwAACAAQAAgAEAAIABAAAAAAAAACIGA91uD9EYRlzIBT6yNU2S2L/wvOA0/em4ocaM//veOtN2GGDzoLMwAACAAQAAgAEAAIABAAAAAAAAAAAA"
+ },
+ {
+ "name": "p2wsh-2of3-none",
+ "psbt": "cHNidP8BAF4BAAAAAcbYkt1iwOa6IsI8lrNx1DWQCCg/y7\u002BfQTlfEhDIKOWVAAAAAAD/////AeiN9QUAAAAAIgAgg\u002BaofANl6wKKByTgFl5yBnqUK8f7sn4ULhAAIJb1C0cAAAAATwEENYfPAy8RJCyAAAAB/DvuQjoBjOttImoGYyiO0Pte4PqdeQqzcNAw4Ecw5sgDgI4uHNSCvdBxlpQ8WoEz0WmvhgIra7A4F3FkTsB0RNcQH8zk3jAAAIABAACAAQAAgE8BBDWHzwNWrAP0gAAAAfkIrkpmsP\u002BhqxS1WvDOSPKnAiXLkBCQLWkBr5C5Po\u002BBAlGvFeBbuLfqwYlbP19H/\u002B/s2DIaAu8iKY\u002BJ0KIDffBgEGDzoLMwAACAAQAAgAEAAIBPAQQ1h88DfblGjYAAAAH1InDHaHo6\u002BzUe9PG5owwQ87bTkhcGg66pSIwTmhHJmAMiI4UjOOpn\u002B/2Nw1KrJiXnmid2RiEja/HAITCQ00ienxDtAhDIMAAAgAEAAIABAACAAAEBK2SQ9QUAAAAAIgAguNLINpkV//IIFd1ti2ig15\u002B6mPOhNWykV0mwsneO9FciAgMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BkcwRAIgPcwj6yaA6RZn\u002B4YSHi4S1WE5ziHEt0IZO5KqDE5B0zMCID6cSLumR2AbgwqMTI3/Z3szEyMQauxtzvBpY8Z4oSp8AgEDBAIAAAABBWlSIQMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BiEDgTQLkhqca3brBTunNmjIsb4WEsFryTwd3BH/ZPS4KkohA91uD9EYRlzIBT6yNU2S2L/wvOA0/em4ocaM//veOtN2U64iBgMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BhgfzOTeMAAAgAEAAIABAACAAQAAAAAAAAAiBgOBNAuSGpxrdusFO6c2aMixvhYSwWvJPB3cEf9k9LgqShjtAhDIMAAAgAEAAIABAACAAQAAAAAAAAAiBgPdbg/RGEZcyAU\u002BsjVNkti/8LzgNP3puKHGjP/73jrTdhhg86CzMAAAgAEAAIABAACAAQAAAAAAAAAAAA==",
+ "sigHash": "None",
+ "network": "Regtest",
+ "expected": "cHNidP8BAF4BAAAAAcbYkt1iwOa6IsI8lrNx1DWQCCg/y7\u002BfQTlfEhDIKOWVAAAAAAD/////AeiN9QUAAAAAIgAgg\u002BaofANl6wKKByTgFl5yBnqUK8f7sn4ULhAAIJb1C0cAAAAATwEENYfPAy8RJCyAAAAB/DvuQjoBjOttImoGYyiO0Pte4PqdeQqzcNAw4Ecw5sgDgI4uHNSCvdBxlpQ8WoEz0WmvhgIra7A4F3FkTsB0RNcQH8zk3jAAAIABAACAAQAAgE8BBDWHzwNWrAP0gAAAAfkIrkpmsP\u002BhqxS1WvDOSPKnAiXLkBCQLWkBr5C5Po\u002BBAlGvFeBbuLfqwYlbP19H/\u002B/s2DIaAu8iKY\u002BJ0KIDffBgEGDzoLMwAACAAQAAgAEAAIBPAQQ1h88DfblGjYAAAAH1InDHaHo6\u002BzUe9PG5owwQ87bTkhcGg66pSIwTmhHJmAMiI4UjOOpn\u002B/2Nw1KrJiXnmid2RiEja/HAITCQ00ienxDtAhDIMAAAgAEAAIABAACAAAEBK2SQ9QUAAAAAIgAguNLINpkV//IIFd1ti2ig15\u002B6mPOhNWykV0mwsneO9FciAgMnQqNaMT2Yz47ME\u002BCqhsEMK9fB1sQRGvbBQkPau524BkcwRAIgPcwj6yaA6RZn\u002B4YSHi4S1WE5ziHEt0IZO5KqDE5B0zMCID6cSLumR2AbgwqMTI3/Z3szEyMQauxtzvBpY8Z4oSp8AiICA4E0C5IanGt26wU7pzZoyLG\u002BFhLBa8k8HdwR/2T0uCpKRzBEAiBFGR\u002BvauPZsjnrTMioqL3pSpWJwNdLxl4O84IrB/NajgIge3JAi2Yynykj2USpjdTQu2/Da/Vv2FuvG20RKrd/\u002B3ABAQMEAgAAAAEFaVIhAydCo1oxPZjPjswT4KqGwQwr18HWxBEa9sFCQ9q7nbgGIQOBNAuSGpxrdusFO6c2aMixvhYSwWvJPB3cEf9k9LgqSiED3W4P0RhGXMgFPrI1TZLYv/C84DT96bihxoz/\u002B94603ZTriIGAydCo1oxPZjPjswT4KqGwQwr18HWxBEa9sFCQ9q7nbgGGB/M5N4wAACAAQAAgAEAAIABAAAAAAAAACIGA4E0C5IanGt26wU7pzZoyLG\u002BFhLBa8k8HdwR/2T0uCpKGO0CEMgwAACAAQAAgAEAAIABAAAAAAAAACIGA91uD9EYRlzIBT6yNU2S2L/wvOA0/em4ocaM//veOtN2GGDzoLMwAACAAQAAgAEAAIABAAAAAAAAAAAA"
+ },
+ {
+ "name": "p2wpkh-3in-all",
+ "psbt": "cHNidP8BAKQBAAAAAwXAGAr1uq/i06r\u002BEW2SjFMKQp3Pg0q\u002BeJcqQ9iWKLrMAAAAAAD/////E9fa5RGuwTHL6xLgYpdDDXz2piFg7F9UWPZXyAZdM8kAAAAAAP////9YszwapNpRRrI7LFJglswjr9SLkao\u002BywZq/AtjZMDChAAAAAAA/////wGsJgMGAAAAABYAFJO3OqgJq4Mr3qWsDV1YUNj0aDHQAAAAAE8BBDWHzwN9uUaNAAAAAIDetxqi8U7tfzci9EleGtB59Z/A84PlsnvZ229emSEgA6/rPqXCpw3EqihylkpeL/EXKvNGahv\u002B0Dm2JmVJf8VGEO0CEMgwAACAAQAAgAAAAAAAAQEfjGkeAAAAAAAWABQCmza03sKejExNXjBVHR8UyJJWpgEDBAIAAAAiBgIUCFqogmf/kpcaV\u002B42XlzRzx4OWdqxWDesHZkVuK70sBjtAhDIMAAAgAEAAIAAAAAAAAAAAD0AAAAAAQEfyFrXBQAAAAAWABR9cTsoys8smwP2qmjSQM06tKj4fwEDBAIAAAAiBgPzpHxMZtZ1f3rW4L0yyV4gPS45MGMDooXHpvIhAGbvtBjtAhDIMAAAgAEAAIAAAAAAAQAAAC8AAAAAAQEfMGYNAAAAAAAWABRmqBq5qDk2/37GDEq0zM5HXigXjwEDBAIAAAAiBgJr4vl26F2PI9F3JT63vX1qltyDoaAOZ/D212UNJ3u1XhjtAhDIMAAAgAEAAIAAAAAAAQAAADQAAAAAAA==",
+ "sigHash": "All",
+ "network": "Regtest",
+ "expected": "cHNidP8BAKQBAAAAAwXAGAr1uq/i06r\u002BEW2SjFMKQp3Pg0q\u002BeJcqQ9iWKLrMAAAAAAD/////E9fa5RGuwTHL6xLgYpdDDXz2piFg7F9UWPZXyAZdM8kAAAAAAP////9YszwapNpRRrI7LFJglswjr9SLkao\u002BywZq/AtjZMDChAAAAAAA/////wGsJgMGAAAAABYAFJO3OqgJq4Mr3qWsDV1YUNj0aDHQAAAAAE8BBDWHzwN9uUaNAAAAAIDetxqi8U7tfzci9EleGtB59Z/A84PlsnvZ229emSEgA6/rPqXCpw3EqihylkpeL/EXKvNGahv\u002B0Dm2JmVJf8VGEO0CEMgwAACAAQAAgAAAAAAAAQEfjGkeAAAAAAAWABQCmza03sKejExNXjBVHR8UyJJWpiICAhQIWqiCZ/\u002BSlxpX7jZeXNHPHg5Z2rFYN6wdmRW4rvSwRzBEAiBuE2ST1E17NZdYPAV/4UA5rextY1RdM\u002BGKHXNR\u002BBJOwQIgSDC/XEww6tjC0n9/9\u002BMJlZVtOQGAEMJamIG1fSXgt4kBAQMEAQAAACIGAhQIWqiCZ/\u002BSlxpX7jZeXNHPHg5Z2rFYN6wdmRW4rvSwGO0CEMgwAACAAQAAgAAAAAAAAAAAPQAAAAABAR/IWtcFAAAAABYAFH1xOyjKzyybA/aqaNJAzTq0qPh/IgID86R8TGbWdX961uC9MsleID0uOTBjA6KFx6byIQBm77RHMEQCIGegAS50pocdDBKqq9A7CzQ9L8tIZ6d49KIji5uWDUesAiBv0rzp\u002BzbKX4Nv9ERH5LzqoLv6Nsu/qZEMozwmblwWtgEBAwQBAAAAIgYD86R8TGbWdX961uC9MsleID0uOTBjA6KFx6byIQBm77QY7QIQyDAAAIABAACAAAAAAAEAAAAvAAAAAAEBHzBmDQAAAAAAFgAUZqgauag5Nv9\u002BxgxKtMzOR14oF48iAgJr4vl26F2PI9F3JT63vX1qltyDoaAOZ/D212UNJ3u1XkcwRAIgAy3\u002Bo\u002BcTPwG77GTmDGEXmCLZ6Yb\u002BLh8w5yEvDi6uFsoCIE7T/74Wa4Dsc1JpIsQtoJd5N7BiMtCAape40rBFTjz3AQEDBAEAAAAiBgJr4vl26F2PI9F3JT63vX1qltyDoaAOZ/D212UNJ3u1XhjtAhDIMAAAgAEAAIAAAAAAAQAAADQAAAAAAA=="
+ },
+ {
+ "name": "p2wpkh-3in-none",
+ "psbt": "cHNidP8BAKQBAAAAAwXAGAr1uq/i06r\u002BEW2SjFMKQp3Pg0q\u002BeJcqQ9iWKLrMAAAAAAD/////E9fa5RGuwTHL6xLgYpdDDXz2piFg7F9UWPZXyAZdM8kAAAAAAP////9YszwapNpRRrI7LFJglswjr9SLkao\u002BywZq/AtjZMDChAAAAAAA/////wGsJgMGAAAAABYAFJO3OqgJq4Mr3qWsDV1YUNj0aDHQAAAAAE8BBDWHzwN9uUaNAAAAAIDetxqi8U7tfzci9EleGtB59Z/A84PlsnvZ229emSEgA6/rPqXCpw3EqihylkpeL/EXKvNGahv\u002B0Dm2JmVJf8VGEO0CEMgwAACAAQAAgAAAAAAAAQEfjGkeAAAAAAAWABQCmza03sKejExNXjBVHR8UyJJWpgEDBAIAAAAiBgIUCFqogmf/kpcaV\u002B42XlzRzx4OWdqxWDesHZkVuK70sBjtAhDIMAAAgAEAAIAAAAAAAAAAAD0AAAAAAQEfyFrXBQAAAAAWABR9cTsoys8smwP2qmjSQM06tKj4fwEDBAIAAAAiBgPzpHxMZtZ1f3rW4L0yyV4gPS45MGMDooXHpvIhAGbvtBjtAhDIMAAAgAEAAIAAAAAAAQAAAC8AAAAAAQEfMGYNAAAAAAAWABRmqBq5qDk2/37GDEq0zM5HXigXjwEDBAIAAAAiBgJr4vl26F2PI9F3JT63vX1qltyDoaAOZ/D212UNJ3u1XhjtAhDIMAAAgAEAAIAAAAAAAQAAADQAAAAAAA==",
+ "sigHash": "None",
+ "network": "Regtest",
+ "expected": "cHNidP8BAKQBAAAAAwXAGAr1uq/i06r\u002BEW2SjFMKQp3Pg0q\u002BeJcqQ9iWKLrMAAAAAAD/////E9fa5RGuwTHL6xLgYpdDDXz2piFg7F9UWPZXyAZdM8kAAAAAAP////9YszwapNpRRrI7LFJglswjr9SLkao\u002BywZq/AtjZMDChAAAAAAA/////wGsJgMGAAAAABYAFJO3OqgJq4Mr3qWsDV1YUNj0aDHQAAAAAE8BBDWHzwN9uUaNAAAAAIDetxqi8U7tfzci9EleGtB59Z/A84PlsnvZ229emSEgA6/rPqXCpw3EqihylkpeL/EXKvNGahv\u002B0Dm2JmVJf8VGEO0CEMgwAACAAQAAgAAAAAAAAQEfjGkeAAAAAAAWABQCmza03sKejExNXjBVHR8UyJJWpiICAhQIWqiCZ/\u002BSlxpX7jZeXNHPHg5Z2rFYN6wdmRW4rvSwRzBEAiBuE2ST1E17NZdYPAV/4UA5rextY1RdM\u002BGKHXNR\u002BBJOwQIgSDC/XEww6tjC0n9/9\u002BMJlZVtOQGAEMJamIG1fSXgt4kBAQMEAgAAACIGAhQIWqiCZ/\u002BSlxpX7jZeXNHPHg5Z2rFYN6wdmRW4rvSwGO0CEMgwAACAAQAAgAAAAAAAAAAAPQAAAAABAR/IWtcFAAAAABYAFH1xOyjKzyybA/aqaNJAzTq0qPh/IgID86R8TGbWdX961uC9MsleID0uOTBjA6KFx6byIQBm77RHMEQCIGegAS50pocdDBKqq9A7CzQ9L8tIZ6d49KIji5uWDUesAiBv0rzp\u002BzbKX4Nv9ERH5LzqoLv6Nsu/qZEMozwmblwWtgEBAwQCAAAAIgYD86R8TGbWdX961uC9MsleID0uOTBjA6KFx6byIQBm77QY7QIQyDAAAIABAACAAAAAAAEAAAAvAAAAAAEBHzBmDQAAAAAAFgAUZqgauag5Nv9\u002BxgxKtMzOR14oF48iAgJr4vl26F2PI9F3JT63vX1qltyDoaAOZ/D212UNJ3u1XkcwRAIgAy3\u002Bo\u002BcTPwG77GTmDGEXmCLZ6Yb\u002BLh8w5yEvDi6uFsoCIE7T/74Wa4Dsc1JpIsQtoJd5N7BiMtCAape40rBFTjz3AQEDBAIAAAAiBgJr4vl26F2PI9F3JT63vX1qltyDoaAOZ/D212UNJ3u1XhjtAhDIMAAAgAEAAIAAAAAAAQAAADQAAAAAAA=="
+ },
+ {
+ "name": "p2wpkh-samepath-all",
+ "psbt": "cHNidP8BAIUBAAAAA5AuveJtsmh34KQCGn2QjTVgYZeAP0wBbFfk5ZOGpl0OAAAAAAD/////0qsBGBoCPD41OUI73G9W8Q1hv3z8MkaGRgWlTiutSFwAAAAAAP////\u002B7IC\u002BzZyJI1BBQ/ghKrJi18xK\u002BL7VVg1Q/B4ohnnMFsAEAAAAA/////wAAAAAATwEENYfPA325Ro0AAAABg9H86IDUttPPFss\u002B9te\u002B0DByQgbeD7RPXNuVH9mh1qIDnMEWyKA\u002BkvyG038on8\u002BHxI\u002B9AD8r6ZI1dNIDSGC8824Q7QIQyDAAAIABAACAAQAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nAQMEAgAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nAQMEAgAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nAQMEAgAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAA=",
+ "sigHash": "All",
+ "network": "Regtest",
+ "expected": "cHNidP8BAIUBAAAAA5AuveJtsmh34KQCGn2QjTVgYZeAP0wBbFfk5ZOGpl0OAAAAAAD/////0qsBGBoCPD41OUI73G9W8Q1hv3z8MkaGRgWlTiutSFwAAAAAAP////\u002B7IC\u002BzZyJI1BBQ/ghKrJi18xK\u002BL7VVg1Q/B4ohnnMFsAEAAAAA/////wAAAAAATwEENYfPA325Ro0AAAABg9H86IDUttPPFss\u002B9te\u002B0DByQgbeD7RPXNuVH9mh1qIDnMEWyKA\u002BkvyG038on8\u002BHxI\u002B9AD8r6ZI1dNIDSGC8824Q7QIQyDAAAIABAACAAQAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nIgIC8VHrR43do2EE/b9zhwf2K1hb5QUCrsXVsN8W/jSatGJHMEQCICzxabvK1oB8RUMHyd5pCg5jnOaAM70BuiKN\u002BoLJhhs8AiAW6D9va0tjfvk/HccVzYx6tTnCziZZnsG4OOHAZElD1AEBAwQBAAAAIgYC8VHrR43do2EE/b9zhwf2K1hb5QUCrsXVsN8W/jSatGIY7QIQyDAAAIABAACAAQAAAAAAAAAzAAAAAAEBH6CGAQAAAAAAFgAUFCm1zrMBqz\u002BqOWbHoyE42PJ3XWciAgLxUetHjd2jYQT9v3OHB/YrWFvlBQKuxdWw3xb\u002BNJq0YkcwRAIgZ5FQCVtOe8P5tBamiWC3rXZ8vL3xmKicHWaNy2jL3ScCIEYkW4Bdhp\u002BuFkga/AiZafMYzi7y/rlA2M52rQjCjLeGAQEDBAEAAAAiBgLxUetHjd2jYQT9v3OHB/YrWFvlBQKuxdWw3xb\u002BNJq0YhjtAhDIMAAAgAEAAIABAAAAAAAAADMAAAAAAQEfoIYBAAAAAAAWABQUKbXOswGrP6o5ZsejITjY8nddZyICAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiRzBEAiA1M\u002BMgTmvnqdi2JnE540v5E1L6jyshtwUy991Ws3FtFAIgWdjj75lK4X5EPV8SqAASzKhuPYd\u002BOxZ946FTrS\u002BqHIUBAQMEAQAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAA="
+ },
+ {
+ "name": "p2wpkh-samepath-none",
+ "psbt": "cHNidP8BAIUBAAAAA5AuveJtsmh34KQCGn2QjTVgYZeAP0wBbFfk5ZOGpl0OAAAAAAD/////0qsBGBoCPD41OUI73G9W8Q1hv3z8MkaGRgWlTiutSFwAAAAAAP////\u002B7IC\u002BzZyJI1BBQ/ghKrJi18xK\u002BL7VVg1Q/B4ohnnMFsAEAAAAA/////wAAAAAATwEENYfPA325Ro0AAAABg9H86IDUttPPFss\u002B9te\u002B0DByQgbeD7RPXNuVH9mh1qIDnMEWyKA\u002BkvyG038on8\u002BHxI\u002B9AD8r6ZI1dNIDSGC8824Q7QIQyDAAAIABAACAAQAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nAQMEAgAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nAQMEAgAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nAQMEAgAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAA=",
+ "sigHash": "None",
+ "network": "Regtest",
+ "expected": "cHNidP8BAIUBAAAAA5AuveJtsmh34KQCGn2QjTVgYZeAP0wBbFfk5ZOGpl0OAAAAAAD/////0qsBGBoCPD41OUI73G9W8Q1hv3z8MkaGRgWlTiutSFwAAAAAAP////\u002B7IC\u002BzZyJI1BBQ/ghKrJi18xK\u002BL7VVg1Q/B4ohnnMFsAEAAAAA/////wAAAAAATwEENYfPA325Ro0AAAABg9H86IDUttPPFss\u002B9te\u002B0DByQgbeD7RPXNuVH9mh1qIDnMEWyKA\u002BkvyG038on8\u002BHxI\u002B9AD8r6ZI1dNIDSGC8824Q7QIQyDAAAIABAACAAQAAAAABAR\u002BghgEAAAAAABYAFBQptc6zAas/qjlmx6MhONjyd11nIgIC8VHrR43do2EE/b9zhwf2K1hb5QUCrsXVsN8W/jSatGJHMEQCICzxabvK1oB8RUMHyd5pCg5jnOaAM70BuiKN\u002BoLJhhs8AiAW6D9va0tjfvk/HccVzYx6tTnCziZZnsG4OOHAZElD1AEBAwQCAAAAIgYC8VHrR43do2EE/b9zhwf2K1hb5QUCrsXVsN8W/jSatGIY7QIQyDAAAIABAACAAQAAAAAAAAAzAAAAAAEBH6CGAQAAAAAAFgAUFCm1zrMBqz\u002BqOWbHoyE42PJ3XWciAgLxUetHjd2jYQT9v3OHB/YrWFvlBQKuxdWw3xb\u002BNJq0YkcwRAIgZ5FQCVtOe8P5tBamiWC3rXZ8vL3xmKicHWaNy2jL3ScCIEYkW4Bdhp\u002BuFkga/AiZafMYzi7y/rlA2M52rQjCjLeGAQEDBAIAAAAiBgLxUetHjd2jYQT9v3OHB/YrWFvlBQKuxdWw3xb\u002BNJq0YhjtAhDIMAAAgAEAAIABAAAAAAAAADMAAAAAAQEfoIYBAAAAAAAWABQUKbXOswGrP6o5ZsejITjY8nddZyICAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiRzBEAiA1M\u002BMgTmvnqdi2JnE540v5E1L6jyshtwUy991Ws3FtFAIgWdjj75lK4X5EPV8SqAASzKhuPYd\u002BOxZ946FTrS\u002BqHIUBAQMEAgAAACIGAvFR60eN3aNhBP2/c4cH9itYW\u002BUFAq7F1bDfFv40mrRiGO0CEMgwAACAAQAAgAEAAAAAAAAAMwAAAAA="
+ }
+]
diff --git a/RemoteSigner.Tests/FunctionTest.cs b/RemoteSigner.Tests/FunctionTest.cs
index a3388e5..b5dd05b 100644
--- a/RemoteSigner.Tests/FunctionTest.cs
+++ b/RemoteSigner.Tests/FunctionTest.cs
@@ -7,6 +7,7 @@
namespace RemoteSigner.Tests;
+[Collection(SignerEnvironment.Collection)]
public class FunctionTest : IDisposable
{
private const string awsKmsKeyId = "mrk-51c7f523a83442718f2f71a986b7c7fb";
diff --git a/RemoteSigner.Tests/GoldenSignatureTests.cs b/RemoteSigner.Tests/GoldenSignatureTests.cs
new file mode 100644
index 0000000..e91d73f
--- /dev/null
+++ b/RemoteSigner.Tests/GoldenSignatureTests.cs
@@ -0,0 +1,73 @@
+using System.Text.Json;
+using FluentAssertions;
+using NBitcoin;
+using Xunit;
+
+namespace RemoteSigner.Tests;
+
+///
+/// Byte-for-byte pins of the PSBT signer's output. ECDSA signatures are deterministic (RFC 6979),
+/// so a dependency bump (NBitcoin) or a refactor of the request path must reproduce exactly these
+/// PSBTs. Regenerate only on purpose: GOLDEN_UPDATE=1 dotnet test --filter GoldenSignatureTests.
+///
+[Collection(SignerEnvironment.Collection)]
+public class GoldenSignatureTests : IDisposable
+{
+ // SignPSBT only signs inputs whose fingerprint has an MF_ entry; the seed comes from the delegate.
+ public GoldenSignatureTests() => Environment.SetEnvironmentVariable("MF_ed0210c8", "{}");
+
+ public void Dispose() => Environment.SetEnvironmentVariable("MF_ed0210c8", null);
+
+ private const string Mnemonic =
+ "middle teach digital prefer fiscal theory syrup enter crash muffin easily anxiety ill barely eagle swim volume consider dynamic unaware deputy middle into physical";
+
+ private static readonly string FixturePath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "golden-psbt-signatures.json");
+
+ public static IEnumerable