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 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(golden.SigHash), + _ => Task.FromResult(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>(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 Load() => + JsonSerializer.Deserialize>(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; } + } +} + +/// Tests that set process-wide MF_ env vars must not run in parallel with each other. +[CollectionDefinition(Collection)] +public sealed class SignerEnvironment +{ + public const string Collection = "Signer environment variables"; +} diff --git a/RemoteSigner.Tests/RemoteSigner.Tests.csproj b/RemoteSigner.Tests/RemoteSigner.Tests.csproj index 4e96afb..ce23967 100644 --- a/RemoteSigner.Tests/RemoteSigner.Tests.csproj +++ b/RemoteSigner.Tests/RemoteSigner.Tests.csproj @@ -20,4 +20,7 @@ + + + \ No newline at end of file diff --git a/RemoteSigner/Dockerfile b/RemoteSigner/Dockerfile index 5f81a73..a414e29 100644 --- a/RemoteSigner/Dockerfile +++ b/RemoteSigner/Dockerfile @@ -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 -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 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 . \ No newline at end of file +COPY --from=build /app/publish . diff --git a/RemoteSigner/RemoteSigner.csproj b/RemoteSigner/RemoteSigner.csproj index e443ca2..79bf896 100644 --- a/RemoteSigner/RemoteSigner.csproj +++ b/RemoteSigner/RemoteSigner.csproj @@ -18,6 +18,14 @@ - + + + + + + + + \ No newline at end of file diff --git a/RemoteSigner/aws-lambda-tools-defaults.json b/RemoteSigner/aws-lambda-tools-defaults.json index 7c16616..c905ac7 100644 --- a/RemoteSigner/aws-lambda-tools-defaults.json +++ b/RemoteSigner/aws-lambda-tools-defaults.json @@ -23,5 +23,6 @@ "tracing-mode" : "PassThrough", "environment-variables" : "\"MF_ed0210c8\"=\"{\"EncryptedSeedphrase\":\"AQICAHheBtxW\\u002B2iTBvvhvmRXaxaScHh6up1/VWCRSMlopexrdwE1C/ylXBL5pmjJ3P/UG7XnAAABBzCCAQMGCSqGSIb3DQEHBqCB9TCB8gIBADCB7AYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxPlkxPX65p7aRcXykCARCAgb4En2Bb/nWQ6m4i3JDP\\u002BKGjaGDAVF4LR6\\u002B2Ljl7orp6pfZbCCxK6e89OBpJWi7elQM670vD/SWkYSZ9MUWUshU8n7NyBJZuZgBhtaH6j6yDhgHtBv7cwJngv0d72QEaTrH2YqLCVuoddEKEpB13ezfkf56230QD134kcJze4fITQGA6sXxQ0x\\u002BWjKOeYltpB\\u002BShk4\\u002BkaNja42ZM0MMjyrMOmQtXCkgdoTUVi6twiqU\\u002Bqr8mQEEq0aNdZzlLCI/v\",\"AwsKmsKeyId\":\"mrk-cec3e3ef59bc4616a6f44da60bfea0ba\"}\"", "dockerfile" : "Dockerfile", + "docker-build-working-dir" : "..", "image-tag" : "nodeguardremotesigner:latest" } \ No newline at end of file diff --git a/justfile b/justfile index ec32d0b..e7049fb 100755 --- a/justfile +++ b/justfile @@ -2,8 +2,9 @@ set positional-arguments build-docker-image: - # Navigate to the RemoteSigner directory and build the Docker image - cd RemoteSigner && docker build -t 839166930136.dkr.ecr.eu-central-1.amazonaws.com/nodeguardremotesigner:latest . + # Build from the repository root: the image needs the vendor/nspark submodule + git submodule update --init vendor/nspark + docker build -f RemoteSigner/Dockerfile -t 839166930136.dkr.ecr.eu-central-1.amazonaws.com/nodeguardremotesigner:latest . push-docker-image: # Push the Docker image to the repository diff --git a/vendor/nspark b/vendor/nspark new file mode 160000 index 0000000..5a5fe8c --- /dev/null +++ b/vendor/nspark @@ -0,0 +1 @@ +Subproject commit 5a5fe8cf231bfa061412efa8c726da714f9e2d88