2019-09-15 21:20:05 +00:00
package applications
import (
"crypto/sha256"
2021-04-09 00:55:17 +00:00
"git.openprivacy.ca/cwtch.im/tapir"
"git.openprivacy.ca/cwtch.im/tapir/primitives/core"
2020-02-06 23:54:13 +00:00
"git.openprivacy.ca/openprivacy/log"
2019-09-15 21:20:05 +00:00
)
// 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 {
2021-05-13 19:37:42 +00:00
if len ( solution ) != 32 {
return false
}
2019-09-15 21:20:05 +00:00
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
}