103 lines
3.6 KiB
Go
103 lines
3.6 KiB
Go
package applications
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"git.openprivacy.ca/cwtch.im/tapir"
|
|
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
|
|
"git.openprivacy.ca/openprivacy/log"
|
|
)
|
|
|
|
// ProofOfWorkApplication forces the incoming connection to do proof of work before granting a capability
|
|
type ProofOfWorkApplication struct {
|
|
TranscriptApp
|
|
}
|
|
|
|
// transcript constants
|
|
const (
|
|
PoWApp = "pow-app"
|
|
PoWSeed = "pow-seed"
|
|
PoWChallenge = "pow-challenge"
|
|
PoWPRNG = "pow-prng"
|
|
PoWSolution = "pow-solution"
|
|
)
|
|
|
|
// SuccessfulProofOfWorkCapability is given when a successfully PoW Challenge has been Completed
|
|
const SuccessfulProofOfWorkCapability = tapir.Capability("SuccessfulProofOfWorkCapability")
|
|
|
|
// NewInstance should always return a new instantiation of the application.
|
|
func (powapp *ProofOfWorkApplication) NewInstance() tapir.Application {
|
|
return new(ProofOfWorkApplication)
|
|
}
|
|
|
|
// Init is run when the connection is first started.
|
|
func (powapp *ProofOfWorkApplication) Init(connection tapir.Connection) {
|
|
powapp.Transcript().NewProtocol(PoWApp)
|
|
if connection.IsOutbound() {
|
|
powapp.Transcript().AddToTranscript(PoWSeed, connection.Expect())
|
|
solution := powapp.solveChallenge(powapp.Transcript().CommitToTranscript(PoWChallenge), powapp.transcript.CommitToPRNG(PoWPRNG))
|
|
powapp.transcript.AddToTranscript(PoWSolution, solution)
|
|
connection.Send(solution)
|
|
connection.SetCapability(SuccessfulProofOfWorkCapability) // We can self grant.because the server will close the connection on failure
|
|
return
|
|
}
|
|
|
|
// We may be the first application, in which case we need to randomize the transcript challenge
|
|
// We use the random hostname of the inbound server (if we've authenticated them then the challenge will
|
|
// already be sufficiently randomized, so this doesn't hurt)
|
|
// It does sadly mean an additional round trip.
|
|
powapp.Transcript().AddToTranscript(PoWSeed, []byte(connection.Hostname()))
|
|
connection.Send([]byte(connection.Hostname()))
|
|
solution := connection.Expect()
|
|
challenge := powapp.Transcript().CommitToTranscript(PoWChallenge)
|
|
// 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)
|
|
powapp.transcript.CommitToPRNG(PoWPRNG)
|
|
powapp.transcript.AddToTranscript(PoWSolution, solution)
|
|
if powapp.validateChallenge(challenge, solution) {
|
|
connection.SetCapability(SuccessfulProofOfWorkCapability)
|
|
return
|
|
}
|
|
}
|
|
|
|
// SolveChallenge takes in a challenge and a message and returns a solution
|
|
// The solution is a 24 byte nonce which when hashed with the challenge and the message
|
|
// produces a sha256 hash with Difficulty leading 0s
|
|
func (powapp *ProofOfWorkApplication) solveChallenge(challenge []byte, prng core.PRNG) []byte {
|
|
solved := false
|
|
var sum [32]byte
|
|
solution := []byte{}
|
|
solve := make([]byte, len(challenge)+32)
|
|
for !solved {
|
|
|
|
solution = prng.Next().Encode(nil)
|
|
|
|
copy(solve[0:], solution[:])
|
|
copy(solve[len(solution):], challenge[:])
|
|
sum = sha256.Sum256(solve)
|
|
|
|
solved = true
|
|
for i := 0; i < 2; i++ {
|
|
if sum[i] != 0x00 {
|
|
solved = false
|
|
}
|
|
}
|
|
}
|
|
log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum)
|
|
return solution[:]
|
|
}
|
|
|
|
// ValidateChallenge returns true if the message and spamguard pass the challenge
|
|
func (powapp *ProofOfWorkApplication) validateChallenge(challenge []byte, solution []byte) bool {
|
|
solve := make([]byte, len(challenge)+32)
|
|
copy(solve[0:], solution[0:32])
|
|
copy(solve[32:], challenge[:])
|
|
sum := sha256.Sum256(solve)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
if sum[i] != 0x00 {
|
|
return false
|
|
}
|
|
}
|
|
log.Debugf("Validated Challenge %v: %v %v\n", challenge, solution, sum)
|
|
return true
|
|
}
|