Skip to content
Draft
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
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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/
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true

- name: Setup .NET
uses: actions/setup-dotnet@v4
Expand All @@ -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
Expand All @@ -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 }}
Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "vendor/nspark"]
path = vendor/nspark
url = https://github.com/Elenpay/nspark.git
branch = main
44 changes: 44 additions & 0 deletions RemoteSigner.Tests/Fixtures/golden-psbt-signatures.json
Original file line number Diff line number Diff line change
@@ -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="
}
]
1 change: 1 addition & 0 deletions RemoteSigner.Tests/FunctionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace RemoteSigner.Tests;

[Collection(SignerEnvironment.Collection)]
public class FunctionTest : IDisposable
{
private const string awsKmsKeyId = "mrk-51c7f523a83442718f2f71a986b7c7fb";
Expand Down Expand Up @@ -51,11 +52,11 @@
sigHash,
network);

Func<RootedKeyPath?, Task<string?>> GetSeed = (_) => Task.FromResult("middle teach digital prefer fiscal theory syrup enter crash muffin easily anxiety ill barely eagle swim volume consider dynamic unaware deputy middle into physical");

Check warning on line 55 in RemoteSigner.Tests/FunctionTest.cs

View workflow job for this annotation

GitHub Actions / Run tests

Nullability of reference types in value of type 'Task<string>' doesn't match target type 'Task<string?>'.
//Act
var result = await function.SignPSBT(psbt, network, sigHash, GetSeed);

var parsedPSBT = PSBT.Parse(result.Psbt ?? throw new InvalidOperationException(), Function.ParseNetwork(network));

Check warning on line 59 in RemoteSigner.Tests/FunctionTest.cs

View workflow job for this annotation

GitHub Actions / Run tests

Dereference of a possibly null reference.

//Assert
result.Should().NotBeNull();
Expand Down Expand Up @@ -128,7 +129,7 @@
}
}

Func<RootedKeyPath?, Task<string?>> GetSeed = (_) => Task.FromResult("middle teach digital prefer fiscal theory syrup enter crash muffin easily anxiety ill barely eagle swim volume consider dynamic unaware deputy middle into physical");

Check warning on line 132 in RemoteSigner.Tests/FunctionTest.cs

View workflow job for this annotation

GitHub Actions / Run tests

Nullability of reference types in value of type 'Task<string>' doesn't match target type 'Task<string?>'.
//Act
var act = () => function.SignPSBT(psbt.ToBase64(), "Regtest", SigHash.All, GetSeed);

Expand Down Expand Up @@ -165,14 +166,14 @@

var originalPSBT = PSBT.Parse(MultisigPsbt, Network.RegTest);

Func<RootedKeyPath?, Task<string?>> GetSeed = (_) => Task.FromResult("middle teach digital prefer fiscal theory syrup enter crash muffin easily anxiety ill barely eagle swim volume consider dynamic unaware deputy middle into physical");

Check warning on line 169 in RemoteSigner.Tests/FunctionTest.cs

View workflow job for this annotation

GitHub Actions / Run tests

Nullability of reference types in value of type 'Task<string>' doesn't match target type 'Task<string?>'.

//Act
var result = await function.SignPSBT(MultisigPsbt, "Regtest", SigHash.All, GetSeed);

//Assert
result.Should().NotBeNull();
var parsedPSBT = PSBT.Parse(result.Psbt ?? throw new InvalidOperationException(), Network.RegTest);

Check warning on line 176 in RemoteSigner.Tests/FunctionTest.cs

View workflow job for this annotation

GitHub Actions / Run tests

Dereference of a possibly null reference.
parsedPSBT.Inputs.Sum(x => x.PartialSigs.Count).Should()
.BeGreaterThan(originalPSBT.Inputs.Sum(x => x.PartialSigs.Count));
}
Expand Down
73 changes: 73 additions & 0 deletions RemoteSigner.Tests/GoldenSignatureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Text.Json;
using FluentAssertions;
using NBitcoin;
using Xunit;

namespace RemoteSigner.Tests;

/// <summary>
/// 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: <c>GOLDEN_UPDATE=1 dotnet test --filter GoldenSignatureTests</c>.
/// </summary>
[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<object[]> Cases() =>
Load().Select(c => new object[] { c.Name });

[Theory]
[MemberData(nameof(Cases))]
public async Task SignPSBT_IsByteIdenticalToTheGoldenOutput(string name)
{
var all = Load();
var golden = all.Single(c => c.Name == name);

var result = await new Function().SignPSBT(golden.Psbt, golden.Network, Enum.Parse<SigHash>(golden.SigHash),
_ => Task.FromResult<string?>(Mnemonic));

if (Environment.GetEnvironmentVariable("GOLDEN_UPDATE") == "1")
{
golden.Expected = result!.Psbt;
var source = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "Fixtures", "golden-psbt-signatures.json");
var onDisk = JsonSerializer.Deserialize<List<GoldenCase>>(await File.ReadAllTextAsync(source), Json)!;
onDisk.Single(c => c.Name == name).Expected = result.Psbt;
await File.WriteAllTextAsync(source, JsonSerializer.Serialize(onDisk, Json) + "\n");
return;
}

golden.Expected.Should().NotBeNullOrEmpty("the golden output must be recorded");
result!.Psbt.Should().Be(golden.Expected);
}

private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web) { WriteIndented = true };

private static List<GoldenCase> Load() =>
JsonSerializer.Deserialize<List<GoldenCase>>(File.ReadAllText(FixturePath), Json)!;

public sealed class GoldenCase
{
public string Name { get; set; } = string.Empty;
public string Psbt { get; set; } = string.Empty;
public string SigHash { get; set; } = string.Empty;
public string Network { get; set; } = string.Empty;
public string? Expected { get; set; }
}
}

/// <summary>Tests that set process-wide MF_ env vars must not run in parallel with each other.</summary>
[CollectionDefinition(Collection)]
public sealed class SignerEnvironment
{
public const string Collection = "Signer environment variables";
}
3 changes: 3 additions & 0 deletions RemoteSigner.Tests/RemoteSigner.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@
<ItemGroup>
<ProjectReference Include="..\RemoteSigner\RemoteSigner.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\*.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
20 changes: 10 additions & 10 deletions RemoteSigner/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Build context: the repository root (NSpark lives in the vendor/nspark submodule).
# docker build -f RemoteSigner/Dockerfile .
FROM --platform=linux/amd64 public.ecr.aws/lambda/dotnet:10 AS base

Check warning on line 3 in RemoteSigner/Dockerfile

View workflow job for this annotation

GitHub Actions / Build Docker image

FROM --platform flag should not use a constant value

FromPlatformFlagConstDisallowed: FROM --platform flag should not use constant value "linux/amd64" More info: https://docs.docker.com/go/dockerfile/rule/from-platform-flag-const-disallowed/

FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:10.0 as build
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:10.0 AS build

Check warning on line 5 in RemoteSigner/Dockerfile

View workflow job for this annotation

GitHub Actions / Build Docker image

FROM --platform flag should not use a constant value

FromPlatformFlagConstDisallowed: FROM --platform flag should not use constant value "linux/amd64" More info: https://docs.docker.com/go/dockerfile/rule/from-platform-flag-const-disallowed/
WORKDIR /src
COPY ["RemoteSigner.csproj", "RemoteSigner/"]
RUN dotnet restore "RemoteSigner/RemoteSigner.csproj"
COPY ["RemoteSigner/RemoteSigner.csproj", "RemoteSigner/"]
COPY ["vendor/nspark/", "vendor/nspark/"]
RUN dotnet restore "RemoteSigner/RemoteSigner.csproj" --runtime linux-x64

WORKDIR "/src/RemoteSigner"
COPY . .
RUN dotnet build "RemoteSigner.csproj" --configuration Release --output /app/build

FROM build AS publish
RUN dotnet publish "RemoteSigner.csproj" \
COPY ["RemoteSigner/", "RemoteSigner/"]
RUN dotnet publish "RemoteSigner/RemoteSigner.csproj" \
--configuration Release \
--runtime linux-x64 \
--self-contained false \
--no-restore \
--output /app/publish \
-p:PublishReadyToRun=true

FROM base AS final
WORKDIR /var/task
COPY --from=publish /app/publish .
COPY --from=build /app/publish .
Loading
Loading