Tapir provides a framework for building Anonymous / metadata resistant Services
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

103 lines
3.6 KiB

  1. package applications
  2. import (
  3. "crypto/sha256"
  4. "cwtch.im/tapir"
  5. "cwtch.im/tapir/primitives/core"
  6. "git.openprivacy.ca/openprivacy/log"
  7. )
  8. // ProofOfWorkApplication forces the incoming connection to do proof of work before granting a capability
  9. type ProofOfWorkApplication struct {
  10. TranscriptApp
  11. }
  12. // transcript constants
  13. const (
  14. PoWApp = "pow-app"
  15. PoWSeed = "pow-seed"
  16. PoWChallenge = "pow-challenge"
  17. PoWPRNG = "pow-prng"
  18. PoWSolution = "pow-solution"
  19. )
  20. // SuccessfulProofOfWorkCapability is given when a successfully PoW Challenge has been Completed
  21. const SuccessfulProofOfWorkCapability = tapir.Capability("SuccessfulProofOfWorkCapability")
  22. // NewInstance should always return a new instantiation of the application.
  23. func (powapp *ProofOfWorkApplication) NewInstance() tapir.Application {
  24. return new(ProofOfWorkApplication)
  25. }
  26. // Init is run when the connection is first started.
  27. func (powapp *ProofOfWorkApplication) Init(connection tapir.Connection) {
  28. powapp.Transcript().NewProtocol(PoWApp)
  29. if connection.IsOutbound() {
  30. powapp.Transcript().AddToTranscript(PoWSeed, connection.Expect())
  31. solution := powapp.solveChallenge(powapp.Transcript().CommitToTranscript(PoWChallenge), powapp.transcript.CommitToPRNG(PoWPRNG))
  32. powapp.transcript.AddToTranscript(PoWSolution, solution)
  33. connection.Send(solution)
  34. connection.SetCapability(SuccessfulProofOfWorkCapability) // We can self grant.because the server will close the connection on failure
  35. return
  36. }
  37. // We may be the first application, in which case we need to randomize the transcript challenge
  38. // We use the random hostname of the inbound server (if we've authenticated them then the challenge will
  39. // already be sufficiently randomized, so this doesn't hurt)
  40. // It does sadly mean an additional round trip.
  41. powapp.Transcript().AddToTranscript(PoWSeed, []byte(connection.Hostname()))
  42. connection.Send([]byte(connection.Hostname()))
  43. solution := connection.Expect()
  44. challenge := powapp.Transcript().CommitToTranscript(PoWChallenge)
  45. // soft-commitment to the prng, doesn't force the client to use it (but we could technically check that it did, not necessary for the security of this App)
  46. powapp.transcript.CommitToPRNG(PoWPRNG)
  47. powapp.transcript.AddToTranscript(PoWSolution, solution)
  48. if powapp.validateChallenge(challenge, solution) {
  49. connection.SetCapability(SuccessfulProofOfWorkCapability)
  50. return
  51. }
  52. }
  53. // SolveChallenge takes in a challenge and a message and returns a solution
  54. // The solution is a 24 byte nonce which when hashed with the challenge and the message
  55. // produces a sha256 hash with Difficulty leading 0s
  56. func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core.PRNG) []byte {
  57. solved := false
  58. var sum [32]byte
  59. solution := []byte{}
  60. solve := make([]byte, len(challenge)+32)
  61. for !solved {
  62. solution = prng.Next().Encode(nil)
  63. copy(solve[0:], solution[:])
  64. copy(solve[len(solution):], challenge[:])
  65. sum = sha256.Sum256(solve)
  66. solved = true
  67. for i := 0; i < 2; i++ {
  68. if sum[i] != 0x00 {
  69. solved = false
  70. }
  71. }
  72. }
  73. log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum)
  74. return solution[:]
  75. }
  76. // ValidateChallenge returns true if the message and spamguard pass the challenge
  77. func (powapp *ProofOfWorkApplication) validateChallenge(challenge []byte, solution []byte) bool {
  78. solve := make([]byte, len(challenge)+32)
  79. copy(solve[0:], solution[0:32])
  80. copy(solve[32:], challenge[:])
  81. sum := sha256.Sum256(solve)
  82. for i := 0; i < 2; i++ {
  83. if sum[i] != 0x00 {
  84. return false
  85. }
  86. }
  87. log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum)
  88. return true
  89. }